From 0f62ccd64afb02615e1bf6151d12735f3dac9987 Mon Sep 17 00:00:00 2001 From: Mikolaj Luzak Date: Thu, 22 Jun 2023 15:55:10 +0200 Subject: [PATCH 1/6] MORE-562: check if observation/intervetion schedule lies within study timeframe --- .../more/studymanager/model/Timeframe.java | 27 +++++++++ .../repository/StudyRepository.java | 32 ++++++++--- .../service/InterventionService.java | 14 +++-- .../service/ObservationService.java | 11 +++- .../studymanager/service/ScheduleService.java | 28 +++++++++ .../service/InterventionServiceTest.java | 2 + .../service/ObservationServiceTest.java | 3 + .../service/ScheduleServiceTest.java | 57 +++++++++++++++++++ 8 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java create mode 100644 studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java create mode 100644 studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java new file mode 100644 index 00000000..eba69c55 --- /dev/null +++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java @@ -0,0 +1,27 @@ +package io.redlink.more.studymanager.model; + +import java.time.LocalDate; + +public class Timeframe { + private LocalDate from; + private LocalDate to; + + + public LocalDate getFrom() { + return from; + } + + public Timeframe setFrom(LocalDate from) { + this.from = from; + return this; + } + + public LocalDate getTo() { + return to; + } + + public Timeframe setTo(LocalDate to) { + this.to = to; + return this; + } +} diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java index 308f588c..948ce180 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java @@ -1,18 +1,18 @@ package io.redlink.more.studymanager.repository; -import io.redlink.more.studymanager.model.Contact; -import io.redlink.more.studymanager.model.Study; -import io.redlink.more.studymanager.model.StudyRole; -import io.redlink.more.studymanager.model.User; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import io.redlink.more.studymanager.exception.BadRequestException; +import io.redlink.more.studymanager.model.*; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Component; +import java.util.List; +import java.util.Optional; +import java.util.Set; + @Component public class StudyRepository { @@ -46,6 +46,7 @@ public class StudyRepository { private static final String SET_PAUSED_STATE_BY_ID = "UPDATE studies SET status = 'paused', modified = now() WHERE study_id = ?"; private static final String SET_CLOSED_STATE_BY_ID = "UPDATE studies SET status = 'closed', end_date = now(), modified = now() WHERE study_id = ?"; private static final String STUDY_HAS_STATE = "SELECT study_id FROM studies WHERE study_id = :study_id AND status::varchar IN (:study_status)"; + private static final String GET_STUDY_TIMEFRAME = "SELECT planned_start_date, planned_end_date FROM studies WHERE study_id = ?"; private final JdbcTemplate template; private final NamedParameterJdbcTemplate namedTemplate; @@ -113,6 +114,17 @@ private String getStatusQuery(Study.Status status) { }; } + public Timeframe getStudyTimeframe(Long studyId) { + try { + return template.queryForObject( + GET_STUDY_TIMEFRAME, + getStudyTimeframeRowMapper(), + studyId); + } catch (EmptyResultDataAccessException e) { + throw new BadRequestException("Study " + studyId + " does not exist"); + } + } + private static MapSqlParameterSource studyToParams(Study study) { return new MapSqlParameterSource() .addValue("title", study.getTitle()) @@ -149,6 +161,12 @@ private static RowMapper getStudyRowMapper() { .setPhoneNumber(rs.getString("contact_phone"))); } + private static RowMapper getStudyTimeframeRowMapper() { + return (rs,rowNum) -> new Timeframe() + .setFrom(RepositoryUtils.readLocalDate(rs, "planned_end_date")) + .setTo(RepositoryUtils.readLocalDate(rs, "planned_end_date")); + } + private static RowMapper getStudyRowMapperWithUserRoles() { return ((rs, rowNum) -> { var study = getStudyRowMapper().mapRow(rs, rowNum); diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java index c838f57c..86c123ce 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java @@ -6,11 +6,8 @@ import io.redlink.more.studymanager.core.validation.ConfigurationValidationReport; import io.redlink.more.studymanager.exception.BadRequestException; import io.redlink.more.studymanager.exception.NotFoundException; -import io.redlink.more.studymanager.model.Action; +import io.redlink.more.studymanager.model.*; import io.redlink.more.studymanager.core.factory.TriggerFactory; -import io.redlink.more.studymanager.model.Intervention; -import io.redlink.more.studymanager.model.Study; -import io.redlink.more.studymanager.model.Trigger; import io.redlink.more.studymanager.repository.InterventionRepository; import io.redlink.more.studymanager.repository.StudyRepository; import io.redlink.more.studymanager.sdk.MoreSDK; @@ -25,6 +22,8 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.List; import java.util.Map; import java.util.Objects; @@ -33,6 +32,7 @@ @Service public class InterventionService { + private final ScheduleService scheduleService; private final StudyStateService studyStateService; private final InterventionRepository repository; private final StudyRepository studyRepository; @@ -43,11 +43,13 @@ public class InterventionService { private static final Logger LOGGER = LoggerFactory.getLogger(InterventionService.class); - public InterventionService(StudyStateService studyStateService, + public InterventionService(ScheduleService scheduleService, + StudyStateService studyStateService, InterventionRepository repository, StudyRepository studyRepository, MoreSDK sdk, Map triggerFactories, Map actionFactories) { + this.scheduleService = scheduleService; this.studyStateService = studyStateService; this.repository = repository; this.studyRepository = studyRepository; @@ -58,6 +60,7 @@ public InterventionService(StudyStateService studyStateService, public Intervention addIntervention(Intervention intervention) { studyStateService.assertStudyNotInState(intervention.getStudyId(), Study.Status.CLOSED); + scheduleService.assertScheduleWithinStudyTime(intervention.getStudyId(), intervention.getSchedule()); return repository.insert(intervention); } @@ -76,6 +79,7 @@ public void deleteIntervention(Long studyId, Integer interventionId) { public Intervention updateIntervention(Intervention intervention) { studyStateService.assertStudyNotInState(intervention.getStudyId(), Study.Status.CLOSED); + scheduleService.assertScheduleWithinStudyTime(intervention.getStudyId(), intervention.getSchedule()); return repository.updateIntervention(intervention); } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java index e541db7c..264dced8 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java @@ -5,12 +5,16 @@ import io.redlink.more.studymanager.core.factory.ObservationFactory; import io.redlink.more.studymanager.exception.BadRequestException; import io.redlink.more.studymanager.exception.NotFoundException; +import io.redlink.more.studymanager.model.Event; import io.redlink.more.studymanager.model.Observation; import io.redlink.more.studymanager.model.Study; +import io.redlink.more.studymanager.model.Timeframe; import io.redlink.more.studymanager.repository.ObservationRepository; import io.redlink.more.studymanager.sdk.MoreSDK; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.List; import java.util.Map; import java.util.Optional; @@ -18,16 +22,19 @@ @Service public class ObservationService { + private final ScheduleService scheduleService; private final StudyStateService studyStateService; private final ObservationRepository repository; private final Map observationFactories; private final MoreSDK sdk; - public ObservationService(StudyStateService studyStateService, + public ObservationService(ScheduleService scheduleService, + StudyStateService studyStateService, ObservationRepository repository, Map observationFactories, MoreSDK sdk) { + this.scheduleService = scheduleService; this.studyStateService = studyStateService; this.repository = repository; this.observationFactories = observationFactories; @@ -36,6 +43,7 @@ public ObservationService(StudyStateService studyStateService, public Observation addObservation(Observation observation) { studyStateService.assertStudyNotInState(observation.getStudyId(), Study.Status.CLOSED); + scheduleService.assertScheduleWithinStudyTime(observation.getStudyId(), observation.getSchedule()); return repository.insert(validate(observation)); } @@ -58,6 +66,7 @@ public List listObservations(Long studyId) { public Observation updateObservation(Observation observation) { studyStateService.assertStudyNotInState(observation.getStudyId(), Study.Status.CLOSED); + scheduleService.assertScheduleWithinStudyTime(observation.getStudyId(), observation.getSchedule()); return repository.updateObservation(validate(observation)); } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java new file mode 100644 index 00000000..c578658b --- /dev/null +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java @@ -0,0 +1,28 @@ +package io.redlink.more.studymanager.service; + +import io.redlink.more.studymanager.exception.BadRequestException; +import io.redlink.more.studymanager.model.Event; +import io.redlink.more.studymanager.model.Timeframe; +import io.redlink.more.studymanager.repository.StudyRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.ZoneId; + +@Service +public class ScheduleService { + private final StudyRepository studyRepository; + + public ScheduleService(StudyRepository studyRepository) { + this.studyRepository = studyRepository; + } + + public Event assertScheduleWithinStudyTime(Long studyId, Event schedule) { + Timeframe timeframe = studyRepository.getStudyTimeframe(studyId); + if(LocalDate.ofInstant(schedule.getDateStart(), ZoneId.systemDefault()).isBefore(timeframe.getFrom()) + || LocalDate.ofInstant(schedule.getDateEnd(), ZoneId.systemDefault()).isAfter(timeframe.getTo())) { + throw new BadRequestException("Schedule should lie within study timeframe"); + } + return schedule; + } +} diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/InterventionServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/InterventionServiceTest.java index 83e60b79..6ed6dc3a 100644 --- a/studymanager/src/test/java/io/redlink/more/studymanager/service/InterventionServiceTest.java +++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/InterventionServiceTest.java @@ -31,6 +31,8 @@ class InterventionServiceTest { StudyPermissionService studyPermissionService; @Mock StudyStateService studyStateService; + @Mock + ScheduleService scheduleService; @InjectMocks InterventionService interventionService; diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/ObservationServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/ObservationServiceTest.java index 53ad363c..bf16e2d6 100644 --- a/studymanager/src/test/java/io/redlink/more/studymanager/service/ObservationServiceTest.java +++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/ObservationServiceTest.java @@ -38,6 +38,9 @@ class ObservationServiceTest { @Mock StudyStateService studyStateService; + @Mock + ScheduleService scheduleService; + @InjectMocks ObservationService observationService; diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java new file mode 100644 index 00000000..5b85257d --- /dev/null +++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java @@ -0,0 +1,57 @@ +package io.redlink.more.studymanager.service; + +import io.redlink.more.studymanager.exception.BadRequestException; +import io.redlink.more.studymanager.model.Event; +import io.redlink.more.studymanager.model.Timeframe; +import io.redlink.more.studymanager.repository.StudyRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Instant; +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class ScheduleServiceTest { + @Mock + StudyRepository studyRepository; + @InjectMocks + ScheduleService scheduleService; + + @Test + void testAssertPasses() { + when(studyRepository.getStudyTimeframe(anyLong())).thenReturn( + new Timeframe() + .setFrom(LocalDate.of(2023, 6, 2)) + .setTo(LocalDate.of(2023,6,3)) + ); + Event schedule = new Event() + .setDateStart(Instant.parse("2023-06-02T00:00:00.00Z")) + .setDateEnd(Instant.parse("2023-06-03T00:00:00.00Z")); + assertThat(scheduleService.assertScheduleWithinStudyTime(1L, schedule)).isEqualTo(schedule); + } + + @Test + void testAssertFails() { + when(studyRepository.getStudyTimeframe(anyLong())).thenReturn( + new Timeframe() + .setFrom(LocalDate.of(2023, 6, 2)) + .setTo(LocalDate.of(2023,6,3)) + ); + Event scheduleBefore = new Event() + .setDateStart(Instant.parse("2023-06-01T00:00:00.00Z")) + .setDateEnd(Instant.parse("2023-06-02T00:00:00.00Z")); + Event scheduleAfter = new Event() + .setDateStart(Instant.parse("2023-06-02T00:00:00.00Z")) + .setDateEnd(Instant.parse("2023-06-04T00:00:00.00Z")); + assertThrows(BadRequestException.class, () -> scheduleService.assertScheduleWithinStudyTime(1L, scheduleBefore)); + assertThrows(BadRequestException.class, () -> scheduleService.assertScheduleWithinStudyTime(1L, scheduleAfter)); + } +} From 3b6c743539b234e7b8f0e5727b231196156f1378 Mon Sep 17 00:00:00 2001 From: Mikolaj Luzak Date: Fri, 23 Jun 2023 15:16:36 +0200 Subject: [PATCH 2/6] MORE-562: requested changes implemented --- .../configuration/TimezoneConfiguration.java | 24 +++++++++++++++++ .../more/studymanager/model/Timeframe.java | 27 +++---------------- .../properties/TimezoneProperties.java | 8 ++++++ .../repository/StudyRepository.java | 6 ++--- .../service/ObservationService.java | 4 --- .../studymanager/service/ScheduleService.java | 8 +++--- .../src/main/resources/application.yaml | 3 +++ .../service/ScheduleServiceTest.java | 24 +++++++++++------ 8 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java create mode 100644 studymanager/src/main/java/io/redlink/more/studymanager/properties/TimezoneProperties.java diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java new file mode 100644 index 00000000..4cd1c0e2 --- /dev/null +++ b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java @@ -0,0 +1,24 @@ +package io.redlink.more.studymanager.configuration; + +import io.redlink.more.studymanager.properties.TimezoneProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.ZoneId; +import java.util.TimeZone; + +@Configuration +@EnableConfigurationProperties({TimezoneProperties.class}) +public class TimezoneConfiguration { + final TimezoneProperties properties; + + public TimezoneConfiguration(TimezoneProperties properties) { + this.properties = properties; + } + + @Bean + public ZoneId ZoneId() { + return TimeZone.getTimeZone(properties.identifier()).toZoneId(); + } +} diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java index eba69c55..c4e82198 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/Timeframe.java @@ -2,26 +2,7 @@ import java.time.LocalDate; -public class Timeframe { - private LocalDate from; - private LocalDate to; - - - public LocalDate getFrom() { - return from; - } - - public Timeframe setFrom(LocalDate from) { - this.from = from; - return this; - } - - public LocalDate getTo() { - return to; - } - - public Timeframe setTo(LocalDate to) { - this.to = to; - return this; - } -} +public record Timeframe ( + LocalDate from, + LocalDate to +) {} diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/properties/TimezoneProperties.java b/studymanager/src/main/java/io/redlink/more/studymanager/properties/TimezoneProperties.java new file mode 100644 index 00000000..fbb3ac1d --- /dev/null +++ b/studymanager/src/main/java/io/redlink/more/studymanager/properties/TimezoneProperties.java @@ -0,0 +1,8 @@ +package io.redlink.more.studymanager.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "more.timezone") +public record TimezoneProperties ( + String identifier +) {} diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java index 948ce180..b402148e 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java @@ -162,9 +162,9 @@ private static RowMapper getStudyRowMapper() { } private static RowMapper getStudyTimeframeRowMapper() { - return (rs,rowNum) -> new Timeframe() - .setFrom(RepositoryUtils.readLocalDate(rs, "planned_end_date")) - .setTo(RepositoryUtils.readLocalDate(rs, "planned_end_date")); + return (rs,rowNum) -> new Timeframe( + RepositoryUtils.readLocalDate(rs, "planned_end_date"), + RepositoryUtils.readLocalDate(rs, "planned_end_date")); } private static RowMapper getStudyRowMapperWithUserRoles() { diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java index 264dced8..0f25ddad 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java @@ -5,16 +5,12 @@ import io.redlink.more.studymanager.core.factory.ObservationFactory; import io.redlink.more.studymanager.exception.BadRequestException; import io.redlink.more.studymanager.exception.NotFoundException; -import io.redlink.more.studymanager.model.Event; import io.redlink.more.studymanager.model.Observation; import io.redlink.more.studymanager.model.Study; -import io.redlink.more.studymanager.model.Timeframe; import io.redlink.more.studymanager.repository.ObservationRepository; import io.redlink.more.studymanager.sdk.MoreSDK; import org.springframework.stereotype.Service; -import java.time.LocalDate; -import java.time.ZoneId; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java index c578658b..6ecc7481 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java @@ -12,15 +12,17 @@ @Service public class ScheduleService { private final StudyRepository studyRepository; + public final ZoneId zoneId; - public ScheduleService(StudyRepository studyRepository) { + public ScheduleService(StudyRepository studyRepository, ZoneId zoneId) { this.studyRepository = studyRepository; + this.zoneId = zoneId; } public Event assertScheduleWithinStudyTime(Long studyId, Event schedule) { Timeframe timeframe = studyRepository.getStudyTimeframe(studyId); - if(LocalDate.ofInstant(schedule.getDateStart(), ZoneId.systemDefault()).isBefore(timeframe.getFrom()) - || LocalDate.ofInstant(schedule.getDateEnd(), ZoneId.systemDefault()).isAfter(timeframe.getTo())) { + if(LocalDate.ofInstant(schedule.getDateStart(), zoneId).isBefore(timeframe.from()) + || LocalDate.ofInstant(schedule.getDateEnd(), zoneId).isAfter(timeframe.to())) { throw new BadRequestException("Schedule should lie within study timeframe"); } return schedule; diff --git a/studymanager/src/main/resources/application.yaml b/studymanager/src/main/resources/application.yaml index 87d0ea39..770eb560 100644 --- a/studymanager/src/main/resources/application.yaml +++ b/studymanager/src/main/resources/application.yaml @@ -101,6 +101,9 @@ more: more-admin: - 'more-admin' + timezone: + identifier: "${MORE_TIMEZONE:Europe/Vienna}" + frontend: title: "${MORE_FE_TITLE:MORE Studymanager}" keycloak: diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java index 5b85257d..725cdb11 100644 --- a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java +++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java @@ -7,8 +7,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import java.time.Instant; import java.time.LocalDate; @@ -19,18 +21,23 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) +@SpringBootTest public class ScheduleServiceTest { - @Mock + + @MockBean StudyRepository studyRepository; + + @Autowired @InjectMocks ScheduleService scheduleService; + @Test void testAssertPasses() { when(studyRepository.getStudyTimeframe(anyLong())).thenReturn( - new Timeframe() - .setFrom(LocalDate.of(2023, 6, 2)) - .setTo(LocalDate.of(2023,6,3)) + new Timeframe( + LocalDate.of(2023, 6, 2), + LocalDate.of(2023,6,3)) ); Event schedule = new Event() .setDateStart(Instant.parse("2023-06-02T00:00:00.00Z")) @@ -40,10 +47,11 @@ void testAssertPasses() { @Test void testAssertFails() { + System.out.println(scheduleService.zoneId); when(studyRepository.getStudyTimeframe(anyLong())).thenReturn( - new Timeframe() - .setFrom(LocalDate.of(2023, 6, 2)) - .setTo(LocalDate.of(2023,6,3)) + new Timeframe( + LocalDate.of(2023, 6, 2), + LocalDate.of(2023,6,3)) ); Event scheduleBefore = new Event() .setDateStart(Instant.parse("2023-06-01T00:00:00.00Z")) From 6c435d4117e13b2704ad2048ededa621db903d53 Mon Sep 17 00:00:00 2001 From: Mikolaj Luzak Date: Fri, 23 Jun 2023 15:53:07 +0200 Subject: [PATCH 3/6] MORE-562: fixed testing error --- .../service/ScheduleServiceTest.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java index 725cdb11..39c6fb12 100644 --- a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java +++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java @@ -1,5 +1,6 @@ package io.redlink.more.studymanager.service; +import io.redlink.more.studymanager.configuration.TimezoneConfiguration; import io.redlink.more.studymanager.exception.BadRequestException; import io.redlink.more.studymanager.model.Event; import io.redlink.more.studymanager.model.Timeframe; @@ -11,6 +12,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfiguration; import java.time.Instant; import java.time.LocalDate; @@ -20,8 +25,13 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @ExtendWith(MockitoExtension.class) -@SpringBootTest +@ContextConfiguration(initializers = ScheduleServiceTest.EnvInitializer.class, + classes = { + ScheduleService.class, + TimezoneConfiguration.class, + }) public class ScheduleServiceTest { @MockBean @@ -62,4 +72,14 @@ void testAssertFails() { assertThrows(BadRequestException.class, () -> scheduleService.assertScheduleWithinStudyTime(1L, scheduleBefore)); assertThrows(BadRequestException.class, () -> scheduleService.assertScheduleWithinStudyTime(1L, scheduleAfter)); } + + static class EnvInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + TestPropertyValues.of( + "more.timeframe.identifier=Europe/Vienna" + ).applyTo(applicationContext); + } + } } From 6f43e24c5f9b01d8ea20c15a39d91c01fd97432b Mon Sep 17 00:00:00 2001 From: Mikolaj Luzak Date: Mon, 26 Jun 2023 09:37:28 +0200 Subject: [PATCH 4/6] MORE-562: impl timezone as bean in other places --- .../configuration/TimezoneConfiguration.java | 5 ++++- .../studymanager/model/transformer/Transformers.java | 11 ++++++++--- .../studymanager/scheduling/SchedulingService.java | 6 ++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java index 4cd1c0e2..75fb75fc 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/TimezoneConfiguration.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.sql.Time; import java.time.ZoneId; import java.util.TimeZone; @@ -19,6 +20,8 @@ public TimezoneConfiguration(TimezoneProperties properties) { @Bean public ZoneId ZoneId() { - return TimeZone.getTimeZone(properties.identifier()).toZoneId(); + return TimeZone().toZoneId(); } + + @Bean public TimeZone TimeZone() { return TimeZone.getTimeZone(properties.identifier()); } } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java index 6e39e833..1bed137c 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java @@ -3,6 +3,8 @@ */ package io.redlink.more.studymanager.model.transformer; +import org.springframework.stereotype.Component; + import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; @@ -11,15 +13,18 @@ /** * Common basic transformers. */ + +@Component public final class Transformers { - private static final ZoneId HOME = ZoneId.of("Europe/Vienna"); + private static ZoneId zoneId; - private Transformers() { + private Transformers(ZoneId zoneId) { + Transformers.zoneId = zoneId; } public static OffsetDateTime toOffsetDateTime(Instant instant) { - return transform(instant, i -> i.atZone(HOME).toOffsetDateTime()); + return transform(instant, i -> i.atZone(zoneId).toOffsetDateTime()); } public static Instant toInstant(OffsetDateTime offsetDateTime) { diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/scheduling/SchedulingService.java b/studymanager/src/main/java/io/redlink/more/studymanager/scheduling/SchedulingService.java index 1c06b5d2..c4601dd2 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/scheduling/SchedulingService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/scheduling/SchedulingService.java @@ -22,10 +22,12 @@ public class SchedulingService { public static final String TRIGGER = "trigger"; public static final String JOB = "job"; private final Scheduler scheduler; + private final TimeZone timeZone; - public SchedulingService(SchedulerFactoryBean factory) throws SchedulerException { + public SchedulingService(SchedulerFactoryBean factory, TimeZone timeZone) throws SchedulerException { this.scheduler = factory.getScheduler(); this.scheduler.start(); + this.timeZone = timeZone; } public String scheduleJob(String issuer, Map data, Schedule schedule, Class type) { @@ -69,7 +71,7 @@ public void preDestroy() throws SchedulerException { private ScheduleBuilder getSchedulerBuilderFor(Schedule schedule) { if(schedule instanceof CronSchedule) { return CronScheduleBuilder.cronSchedule(((CronSchedule) schedule).getCronExpression()) - .inTimeZone(TimeZone.getTimeZone("Europe/Vienna")); //TODO make configurable per study + .inTimeZone(timeZone); //TODO make configurable per study } else { throw new NotImplementedException("SchedulerType " + schedule.getClass().getSimpleName() + " not yet supportet"); } From 49d31931a0dbb994510f04a579599510529726c4 Mon Sep 17 00:00:00 2001 From: Mikolaj Luzak Date: Mon, 26 Jun 2023 11:25:56 +0200 Subject: [PATCH 5/6] MORE-562: some requested changes --- .../studymanager/model/transformer/Transformers.java | 10 +++------- .../more/studymanager/service/ScheduleService.java | 2 +- .../more/studymanager/service/ScheduleServiceTest.java | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java index 1bed137c..6afc0b51 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java @@ -3,8 +3,6 @@ */ package io.redlink.more.studymanager.model.transformer; -import org.springframework.stereotype.Component; - import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; @@ -14,17 +12,15 @@ * Common basic transformers. */ -@Component public final class Transformers { - private static ZoneId zoneId; + private static final ZoneId HOME = ZoneId.of("Europe/Vienna"); - private Transformers(ZoneId zoneId) { - Transformers.zoneId = zoneId; + private Transformers() { } public static OffsetDateTime toOffsetDateTime(Instant instant) { - return transform(instant, i -> i.atZone(zoneId).toOffsetDateTime()); + return transform(instant, i -> i.atZone(HOME).toOffsetDateTime()); } public static Instant toInstant(OffsetDateTime offsetDateTime) { diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java index 6ecc7481..7736457e 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ScheduleService.java @@ -12,7 +12,7 @@ @Service public class ScheduleService { private final StudyRepository studyRepository; - public final ZoneId zoneId; + private final ZoneId zoneId; public ScheduleService(StudyRepository studyRepository, ZoneId zoneId) { this.studyRepository = studyRepository; diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java index 39c6fb12..0f7dd5f6 100644 --- a/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java +++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/ScheduleServiceTest.java @@ -57,7 +57,6 @@ void testAssertPasses() { @Test void testAssertFails() { - System.out.println(scheduleService.zoneId); when(studyRepository.getStudyTimeframe(anyLong())).thenReturn( new Timeframe( LocalDate.of(2023, 6, 2), From 5f8e186b95434a11d941de21fb2e2bbc85868101 Mon Sep 17 00:00:00 2001 From: Mikolaj Luzak Date: Tue, 27 Jun 2023 10:39:39 +0200 Subject: [PATCH 6/6] MORE-562: fixed bug where timeframe check would always throw exception --- .../repository/StudyRepository.java | 2 +- .../service/InterventionService.java | 20 +++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java index b402148e..d7f60f6d 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java @@ -163,7 +163,7 @@ private static RowMapper getStudyRowMapper() { private static RowMapper getStudyTimeframeRowMapper() { return (rs,rowNum) -> new Timeframe( - RepositoryUtils.readLocalDate(rs, "planned_end_date"), + RepositoryUtils.readLocalDate(rs, "planned_start_date"), RepositoryUtils.readLocalDate(rs, "planned_end_date")); } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java index 86c123ce..b27e2920 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java @@ -3,18 +3,18 @@ import io.redlink.more.studymanager.core.component.Component; import io.redlink.more.studymanager.core.exception.ConfigurationValidationException; import io.redlink.more.studymanager.core.factory.ActionFactory; +import io.redlink.more.studymanager.core.factory.TriggerFactory; import io.redlink.more.studymanager.core.validation.ConfigurationValidationReport; import io.redlink.more.studymanager.exception.BadRequestException; import io.redlink.more.studymanager.exception.NotFoundException; -import io.redlink.more.studymanager.model.*; -import io.redlink.more.studymanager.core.factory.TriggerFactory; +import io.redlink.more.studymanager.model.Action; +import io.redlink.more.studymanager.model.Intervention; +import io.redlink.more.studymanager.model.Study; +import io.redlink.more.studymanager.model.Trigger; import io.redlink.more.studymanager.repository.InterventionRepository; import io.redlink.more.studymanager.repository.StudyRepository; import io.redlink.more.studymanager.sdk.MoreSDK; import io.redlink.more.studymanager.utils.LoggingUtils; - -import java.text.ParseException; - import org.quartz.CronExpression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,8 +22,7 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import java.time.LocalDate; -import java.time.ZoneId; +import java.text.ParseException; import java.util.List; import java.util.Map; import java.util.Objects; @@ -32,7 +31,6 @@ @Service public class InterventionService { - private final ScheduleService scheduleService; private final StudyStateService studyStateService; private final InterventionRepository repository; private final StudyRepository studyRepository; @@ -43,13 +41,11 @@ public class InterventionService { private static final Logger LOGGER = LoggerFactory.getLogger(InterventionService.class); - public InterventionService(ScheduleService scheduleService, - StudyStateService studyStateService, + public InterventionService(StudyStateService studyStateService, InterventionRepository repository, StudyRepository studyRepository, MoreSDK sdk, Map triggerFactories, Map actionFactories) { - this.scheduleService = scheduleService; this.studyStateService = studyStateService; this.repository = repository; this.studyRepository = studyRepository; @@ -60,7 +56,6 @@ public InterventionService(ScheduleService scheduleService, public Intervention addIntervention(Intervention intervention) { studyStateService.assertStudyNotInState(intervention.getStudyId(), Study.Status.CLOSED); - scheduleService.assertScheduleWithinStudyTime(intervention.getStudyId(), intervention.getSchedule()); return repository.insert(intervention); } @@ -79,7 +74,6 @@ public void deleteIntervention(Long studyId, Integer interventionId) { public Intervention updateIntervention(Intervention intervention) { studyStateService.assertStudyNotInState(intervention.getStudyId(), Study.Status.CLOSED); - scheduleService.assertScheduleWithinStudyTime(intervention.getStudyId(), intervention.getSchedule()); return repository.updateIntervention(intervention); }