From 759e0810a6ee99ed1dfb7dd3875a5eea1e7f225b Mon Sep 17 00:00:00 2001 From: Laurent Caouissin <38245508+laurentC35@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:20:52 +0100 Subject: [PATCH] feat: restore version (#331) * bump: pogues-model to 1.4.2-SNAPSHOT * chore(Version): switch from Timestamp to ZonedDateTime * test: DateUtils (use in many cases) * chore: manage error when there is no result from db * feat: implementation of restoring version * chore: configure timeZone in props * bump: .9.5-SNAPSHOT * fix: owner missing (bump pogues-model to 1.4.2) * bump: 4.9.5-SNAPSHOT.1 * bump: 4.9.5 --- pom.xml | 4 +- src/main/java/fr/insee/pogues/Pogues.java | 13 +++++ .../pogues/domain/entity/db/Version.java | 4 +- .../persistence/impl/VersionPostgresql.java | 29 ++++++++-- .../persistence/impl/VersionRowMapper.java | 4 +- .../service/QuestionnairesServiceImpl.java | 2 +- .../service/VersionServiceImpl.java | 22 +++++--- .../java/fr/insee/pogues/utils/DateUtils.java | 34 ++++++++++++ .../pogues/utils/json/JSONFunctions.java | 12 ----- .../webservice/rest/VersionController.java | 2 +- src/main/resources/application.yaml | 2 + .../fr/insee/pogues/utils/DateUtilsTest.java | 54 +++++++++++++++++++ .../transforms/PoguesJSONToPoguesXML/out.xml | 2 +- 13 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 src/main/java/fr/insee/pogues/utils/DateUtils.java create mode 100644 src/test/java/fr/insee/pogues/utils/DateUtilsTest.java diff --git a/pom.xml b/pom.xml index 2e4ea835..b24fab77 100644 --- a/pom.xml +++ b/pom.xml @@ -13,14 +13,14 @@ fr.insee Pogues-BO jar - 4.9.4 + 4.9.5 Pogues-Back-Office UTF-8 21 pogues-bo - 1.4.0 + 1.4.2 2.10 2.7.0 0.8.12 diff --git a/src/main/java/fr/insee/pogues/Pogues.java b/src/main/java/fr/insee/pogues/Pogues.java index 0dfcf9c5..aa483bd3 100644 --- a/src/main/java/fr/insee/pogues/Pogues.java +++ b/src/main/java/fr/insee/pogues/Pogues.java @@ -1,7 +1,9 @@ package fr.insee.pogues; import fr.insee.pogues.configuration.PropertiesLogger; +import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -10,12 +12,17 @@ import org.springframework.context.event.EventListener; import org.springframework.transaction.annotation.EnableTransactionManagement; +import java.util.TimeZone; + @SpringBootApplication(scanBasePackages = "fr.insee.pogues") @EnableTransactionManagement @ConfigurationPropertiesScan @Slf4j public class Pogues extends SpringBootServletInitializer { + @Value("${application.timezoneId}") + private String applicationTimeZoneId; + public static SpringApplicationBuilder configureApplicationBuilder(SpringApplicationBuilder springApplicationBuilder){ return springApplicationBuilder.sources(Pogues.class).listeners(new PropertiesLogger()); } @@ -24,6 +31,12 @@ public static void main(String[] args) { configureApplicationBuilder(new SpringApplicationBuilder()).build().run(args); } + @PostConstruct + public void executeAfterMain() { + log.info("Timezone is set to '{}'", applicationTimeZoneId); + TimeZone.setDefault(TimeZone.getTimeZone(applicationTimeZoneId)); + } + @EventListener public void handleApplicationReady(ApplicationReadyEvent event) { log.info("=============== Pogues Back-Office has successfully started. ==============="); diff --git a/src/main/java/fr/insee/pogues/domain/entity/db/Version.java b/src/main/java/fr/insee/pogues/domain/entity/db/Version.java index 177c70bf..fcafa103 100644 --- a/src/main/java/fr/insee/pogues/domain/entity/db/Version.java +++ b/src/main/java/fr/insee/pogues/domain/entity/db/Version.java @@ -9,7 +9,7 @@ import lombok.Setter; import java.sql.Date; -import java.sql.Timestamp; +import java.time.ZonedDateTime; import java.util.UUID; @@ -22,7 +22,7 @@ public class Version { private UUID id; private String poguesId; - private Timestamp timestamp; + private ZonedDateTime timestamp; private Date day; private JsonNode data; private String author; diff --git a/src/main/java/fr/insee/pogues/persistence/impl/VersionPostgresql.java b/src/main/java/fr/insee/pogues/persistence/impl/VersionPostgresql.java index 1269eed4..bf33f87c 100644 --- a/src/main/java/fr/insee/pogues/persistence/impl/VersionPostgresql.java +++ b/src/main/java/fr/insee/pogues/persistence/impl/VersionPostgresql.java @@ -2,16 +2,21 @@ import fr.insee.pogues.domain.entity.db.Version; import fr.insee.pogues.persistence.repository.QuestionnaireVersionRepository; +import fr.insee.pogues.webservice.rest.PoguesException; import lombok.extern.slf4j.Slf4j; import org.postgresql.util.PGobject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import java.util.List; import java.util.UUID; +import static fr.insee.pogues.utils.DateUtils.convertZonedDateTimeToTimestamp; + @Service @Slf4j public class VersionPostgresql implements QuestionnaireVersionRepository { @@ -36,7 +41,12 @@ public List getVersionsByQuestionnaireId(String poguesId, boolean withD String qString = "SELECT " + columns + " FROM pogues_version pv WHERE pv.pogues_id = ? ORDER BY timestamp DESC;"; - return jdbcTemplate.query(qString, new VersionRowMapper(withData), poguesId); + + List versions = jdbcTemplate.query(qString, new VersionRowMapper(withData), poguesId); + if(versions.isEmpty()){ + throw new PoguesException(404, "Not found", "No version for poguesId "+ poguesId); + } + return versions; } @Override @@ -45,7 +55,11 @@ public Version getLastVersionByQuestionnaireId(String poguesId, boolean withData String qString = "SELECT " + columns + " FROM pogues_version pv WHERE pv.pogues_id = ? ORDER BY timestamp DESC LIMIT 1;"; - return jdbcTemplate.queryForObject(qString, new VersionRowMapper(withData), poguesId); + try { + return jdbcTemplate.queryForObject(qString, new VersionRowMapper(withData), poguesId); + } catch (EmptyResultDataAccessException e) { + throw new PoguesException(404, "Not found", "No version for poguesId "+ poguesId); + } } @Override @@ -54,7 +68,11 @@ public Version getVersionByVersionId(UUID versionId, boolean withData) throws Ex String qString = "SELECT " + columns + " FROM pogues_version pv WHERE pv.id = ?;"; - return jdbcTemplate.queryForObject(qString, new VersionRowMapper(withData), versionId); + try { + return jdbcTemplate.queryForObject(qString, new VersionRowMapper(withData), versionId); + } catch (EmptyResultDataAccessException e) { + throw new PoguesException(404, "Not found", "No version with id "+ versionId); + } } @Override @@ -84,7 +102,7 @@ SELECT day, pogues_id, MAX(timestamp) jsonData.setValue(version.getData().toString()); jdbcTemplate.update(qString, // insert request - version.getId(), jsonData, version.getTimestamp(), version.getDay(), version.getPoguesId(), version.getAuthor(), + version.getId(), jsonData, convertZonedDateTimeToTimestamp(version.getTimestamp()), version.getDay(), version.getPoguesId(), version.getAuthor(), // Delete request: we keep last ${maxCurrentVersions} for the current day version.getPoguesId(), version.getDay(), maxCurrentVersions, // Delete request: we keep only the last version for each edited day @@ -95,6 +113,7 @@ SELECT day, pogues_id, MAX(timestamp) @Override public void deleteVersionsByQuestionnaireId(String poguesId) throws Exception { String qString = "DELETE from pogues_version pv WHERE pv.pogues_id = ?;"; - jdbcTemplate.update(qString, poguesId); + int nbVersionsDeleted = jdbcTemplate.update(qString, poguesId); + if(nbVersionsDeleted == 0) throw new PoguesException(404, "Not found", "No version to delete for poguesId "+ poguesId); } } diff --git a/src/main/java/fr/insee/pogues/persistence/impl/VersionRowMapper.java b/src/main/java/fr/insee/pogues/persistence/impl/VersionRowMapper.java index c12dd5ab..c6a3395e 100644 --- a/src/main/java/fr/insee/pogues/persistence/impl/VersionRowMapper.java +++ b/src/main/java/fr/insee/pogues/persistence/impl/VersionRowMapper.java @@ -10,6 +10,8 @@ import java.sql.SQLException; import java.util.UUID; +import static fr.insee.pogues.utils.DateUtils.convertTimestampToZonedDateTime; + @Slf4j public class VersionRowMapper implements RowMapper { private boolean withData; @@ -24,7 +26,7 @@ public Version mapRow(ResultSet rs, int rowNum) throws SQLException { version.setId(UUID.fromString(rs.getString("id"))); version.setPoguesId(rs.getString("pogues_id")); version.setDay(rs.getDate("day")); - version.setTimestamp(rs.getTimestamp("timestamp")); + version.setTimestamp(convertTimestampToZonedDateTime(rs.getTimestamp("timestamp"))); version.setAuthor(rs.getString("author")); if(withData){ try { diff --git a/src/main/java/fr/insee/pogues/persistence/service/QuestionnairesServiceImpl.java b/src/main/java/fr/insee/pogues/persistence/service/QuestionnairesServiceImpl.java index d57f9263..55bcd31e 100644 --- a/src/main/java/fr/insee/pogues/persistence/service/QuestionnairesServiceImpl.java +++ b/src/main/java/fr/insee/pogues/persistence/service/QuestionnairesServiceImpl.java @@ -146,7 +146,7 @@ public void updateJsonLunatic(String id, JsonNode dataLunatic) throws Exception public Questionnaire deReference(JsonNode jsonQuestionnaire) throws Exception { Questionnaire questionnaire = PoguesDeserializer.questionnaireToJavaObject(jsonQuestionnaire); - List references = JSONFunctions.getChildReferencesFromQuestionnaire(jsonQuestionnaire); + List references = questionnaire.getChildQuestionnaireRef(); deReference(references, questionnaire); return questionnaire; } diff --git a/src/main/java/fr/insee/pogues/persistence/service/VersionServiceImpl.java b/src/main/java/fr/insee/pogues/persistence/service/VersionServiceImpl.java index 1d6db0c9..bd695b4a 100644 --- a/src/main/java/fr/insee/pogues/persistence/service/VersionServiceImpl.java +++ b/src/main/java/fr/insee/pogues/persistence/service/VersionServiceImpl.java @@ -2,17 +2,23 @@ import com.fasterxml.jackson.databind.JsonNode; import fr.insee.pogues.domain.entity.db.Version; +import fr.insee.pogues.model.Questionnaire; import fr.insee.pogues.persistence.repository.QuestionnaireRepository; import fr.insee.pogues.persistence.repository.QuestionnaireVersionRepository; +import fr.insee.pogues.utils.DateUtils; +import fr.insee.pogues.utils.PoguesDeserializer; +import fr.insee.pogues.utils.PoguesSerializer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.sql.Date; -import java.sql.Timestamp; import java.time.Instant; +import java.time.ZonedDateTime; import java.util.List; import java.util.UUID; +import static fr.insee.pogues.utils.json.JSONFunctions.jsonStringtoJsonNode; + @Service public class VersionServiceImpl implements VersionService { @@ -50,7 +56,7 @@ public void createVersionOfQuestionnaire(String poguesId, JsonNode data, String Version versionToStore = new Version( UUID.randomUUID(), poguesId, - Timestamp.from(now), + ZonedDateTime.now(), new Date(now.toEpochMilli()), data, author); @@ -66,9 +72,13 @@ public void deleteVersionsByQuestionnaireId(String poguesId) throws Exception { public void restoreVersion(UUID versionId) throws Exception { // (1) Retrieve desired version Version version = questionnaireVersionRepository.getVersionByVersionId(versionId, true); - // (2) Update questionnaire in pogues table - questionnaireRepository.updateQuestionnaire(version.getPoguesId(), version.getData()); - // (3) Create new version - this.createVersionOfQuestionnaire(version.getPoguesId(), version.getData(), version.getAuthor()); + // (2) Update lastUpdatedDate in Pogues-Model + Questionnaire questionnaire = PoguesDeserializer.questionnaireToJavaObject(version.getData()); + questionnaire.setLastUpdatedDate(DateUtils.getIsoDateFromInstant(Instant.now())); + JsonNode newQuestionnaire = jsonStringtoJsonNode(PoguesSerializer.questionnaireJavaToString(questionnaire)); + // (3) Update questionnaire in pogues table + questionnaireRepository.updateQuestionnaire(version.getPoguesId(), newQuestionnaire); + // (4) Create new version + this.createVersionOfQuestionnaire(version.getPoguesId(), newQuestionnaire, version.getAuthor()); } } diff --git a/src/main/java/fr/insee/pogues/utils/DateUtils.java b/src/main/java/fr/insee/pogues/utils/DateUtils.java new file mode 100644 index 00000000..b3abe02c --- /dev/null +++ b/src/main/java/fr/insee/pogues/utils/DateUtils.java @@ -0,0 +1,34 @@ +package fr.insee.pogues.utils; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +public class DateUtils { + + public static ZoneId zoneId = ZoneId.systemDefault(); + public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + /** + * This function is used to get the date in ISO 8601 format Date + * @param instant (can be null) + * @return if date (Instant) is provided, it returns formated Date, if not returns formated of now. + */ + public static String getIsoDateFromInstant(Instant instant){ + if(instant != null) { + return instant.atZone(zoneId).format(formatter); + } + ZonedDateTime zonedDateTimeNow = ZonedDateTime.now(zoneId); + return zonedDateTimeNow.format(formatter); + } + + public static Timestamp convertZonedDateTimeToTimestamp(ZonedDateTime zonedDateTime){ + return Timestamp.from(zonedDateTime.toInstant()); + } + + public static ZonedDateTime convertTimestampToZonedDateTime(Timestamp timestamp){ + return timestamp.toInstant().atZone(ZoneId.systemDefault()); + } +} diff --git a/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java b/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java index fb9682ad..22d01c31 100644 --- a/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java +++ b/src/main/java/fr/insee/pogues/utils/json/JSONFunctions.java @@ -47,16 +47,4 @@ private static JsonNode renameKey(JsonNode input, String key, String replacement inputNode.remove(key); return inputNode; } - - public static List getChildReferencesFromQuestionnaire(JsonNode questionnaire) { - ArrayNode references = (ArrayNode) questionnaire.get("childQuestionnaireRef"); - return IntStream.range(0, references.size()) - .mapToObj(references::get) - .map(JsonNode::asText) - .collect(Collectors.toList()); - } - - - - } diff --git a/src/main/java/fr/insee/pogues/webservice/rest/VersionController.java b/src/main/java/fr/insee/pogues/webservice/rest/VersionController.java index 12078827..0a4013cb 100644 --- a/src/main/java/fr/insee/pogues/webservice/rest/VersionController.java +++ b/src/main/java/fr/insee/pogues/webservice/rest/VersionController.java @@ -55,7 +55,7 @@ public Version getLastVersionByQuestionnaireId( return versionService.getLastVersionByQuestionnaireId(poguesId, withData); } - @PostMapping("questionnaire/restore") + @PostMapping("questionnaire/restore/{versionId}") @Operation( operationId = "restoreVersionByVersion", summary = "Restore an old version according to its id", diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 5b87152c..01b0bf8c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -8,6 +8,8 @@ logging: application: host: localhost:${server.port} + # https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html + timezoneId: "Europe/Paris" name: '' scheme: http public-urls: diff --git a/src/test/java/fr/insee/pogues/utils/DateUtilsTest.java b/src/test/java/fr/insee/pogues/utils/DateUtilsTest.java new file mode 100644 index 00000000..1ecef4df --- /dev/null +++ b/src/test/java/fr/insee/pogues/utils/DateUtilsTest.java @@ -0,0 +1,54 @@ +package fr.insee.pogues.utils; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.Timestamp; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DateUtilsTest { + + @BeforeEach + void setup(){ + TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("Europe/Paris"))); + } + + @Test + void testConversionOfChristmasDate(){ + ZonedDateTime zonedDateTime = ZonedDateTime.of( + 2024,12,25, + 20,45,50,0, + ZoneId.systemDefault()); + Instant instant = zonedDateTime.toInstant(); + assertEquals("2024-12-25T20:45:50.000+0100", DateUtils.getIsoDateFromInstant(instant)); + } + + @Test + void timestampToZonedDateTime(){ + String dateString = "2024-12-25 20:45:50"; + Timestamp timestamp = Timestamp.valueOf(dateString); + ZonedDateTime zonedDateTimeConverted = DateUtils.convertTimestampToZonedDateTime(timestamp); + assertEquals(2024,zonedDateTimeConverted.getYear()); + assertEquals(12,zonedDateTimeConverted.getMonthValue()); + assertEquals(25,zonedDateTimeConverted.getDayOfMonth()); + assertEquals(20, zonedDateTimeConverted.getHour()); + assertEquals(45, zonedDateTimeConverted.getMinute()); + assertEquals(50, zonedDateTimeConverted.getSecond()); + } + + @Test + void zonedDateTimeToTimestamp(){ + ZonedDateTime zonedDateTime = ZonedDateTime.of( + 2024,12,25, + 20,45,50,0, + ZoneId.systemDefault()); + long longZone = zonedDateTime.toInstant().toEpochMilli(); + Timestamp timestamp = DateUtils.convertZonedDateTimeToTimestamp(zonedDateTime); + long longTimestamp = timestamp.getTime(); + assertEquals(longZone, longTimestamp); + } +} diff --git a/src/test/resources/transforms/PoguesJSONToPoguesXML/out.xml b/src/test/resources/transforms/PoguesJSONToPoguesXML/out.xml index c5f88265..9a81df26 100644 --- a/src/test/resources/transforms/PoguesJSONToPoguesXML/out.xml +++ b/src/test/resources/transforms/PoguesJSONToPoguesXML/out.xml @@ -1,5 +1,5 @@ - + SIMPL CAPI