From b552f265c529de23c9ed1e63b5e0995c6ae1a9b9 Mon Sep 17 00:00:00 2001 From: TOURI ANIS Date: Wed, 3 Apr 2024 19:46:11 +0200 Subject: [PATCH 1/2] Empty the trash automatically Signed-off-by: TOURI ANIS --- .../explore/server/ExploreApplication.java | 2 ++ .../server/services/DirectoryService.java | 10 ++++++ .../server/services/ExploreService.java | 13 +++++++ .../server/services/ScheduledCleaner.java | 29 +++++++++++++++ src/main/resources/config/application.yaml | 4 +++ .../gridsuite/explore/server/ExploreTest.java | 36 +++++++++++++++++++ 6 files changed, 94 insertions(+) create mode 100644 src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java diff --git a/src/main/java/org/gridsuite/explore/server/ExploreApplication.java b/src/main/java/org/gridsuite/explore/server/ExploreApplication.java index 84a24643..a0a5e217 100644 --- a/src/main/java/org/gridsuite/explore/server/ExploreApplication.java +++ b/src/main/java/org/gridsuite/explore/server/ExploreApplication.java @@ -8,12 +8,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; /** * @author Etienne Homer */ @SuppressWarnings("checkstyle:HideUtilityClassConstructor") @SpringBootApplication +@EnableScheduling public class ExploreApplication { public static void main(String[] args) { SpringApplication.run(ExploreApplication.class, args); diff --git a/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java b/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java index 3f6ff4ed..a8576355 100644 --- a/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java +++ b/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java @@ -117,6 +117,16 @@ public ElementAttributes getElementInfos(UUID elementUuid) { .getBody(); } + public List getStashedElementInfos(int daysAgo) { + String path = UriComponentsBuilder + .fromPath(ELEMENTS_SERVER_ROOT_PATH + "/stashed") + .queryParam("daysAgo", daysAgo) + .toUriString(); + return restTemplate.exchange(directoryServerBaseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { + }) + .getBody(); + } + private List getElementsInfos(List elementsUuids, List elementTypes) { var ids = elementsUuids.stream().map(UUID::toString).collect(Collectors.joining(",")); String path = UriComponentsBuilder.fromPath(ELEMENTS_SERVER_ROOT_PATH).toUriString() + "?ids=" + ids; diff --git a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java index 4f6c4a49..e3a847df 100644 --- a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java +++ b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import static org.gridsuite.explore.server.ExploreException.Type.NOT_ALLOWED; import static org.gridsuite.explore.server.ExploreException.Type.UNKNOWN_ELEMENT_TYPE; @@ -215,6 +216,18 @@ public void deleteElements(List uuids, String userId) { } } + public void deleteStashedElements(int daysAgo) { + Map> stashedElementsToDelete = directoryService.getStashedElementInfos(daysAgo).stream() + .collect(Collectors.groupingBy( + ElementAttributes::getOwner, + Collectors.mapping( + ElementAttributes::getElementUuid, + Collectors.toList() + ) + )); + stashedElementsToDelete.forEach((userId, uuids) -> deleteElements(uuids, userId)); + } + public void updateFilter(UUID id, String filter, String userId, String name) { filterService.updateFilter(id, filter, userId); updateElementName(id, name, userId); diff --git a/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java b/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java new file mode 100644 index 00000000..95597df1 --- /dev/null +++ b/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java @@ -0,0 +1,29 @@ +package org.gridsuite.explore.server.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +@Service +public class ScheduledCleaner { + private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledCleaner.class); + + private ExploreService exploreService; + + public ScheduledCleaner(ExploreService exploreService) { + this.exploreService = exploreService; + } + + @Scheduled(cron = "${cleaning-stash-cron}", zone = "UTC") + public void deleteStashedExpired() { + ZonedDateTime startZonedDateTime = ZonedDateTime.now(ZoneOffset.UTC); + LOGGER.info("Cleaning cases cron starting execution at {}", startZonedDateTime); + exploreService.deleteStashedElements(30); // delete all stashed elements older than 30 days + ZonedDateTime endZonedDateTime = ZonedDateTime.now(ZoneOffset.UTC); + LOGGER.info("Cleaning cases cron finished execution at {}", endZonedDateTime); + } +} diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 5e4abcc6..a924ba24 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -1,3 +1,7 @@ spring: application: name: explore-server + +# At 01:00 AM every day. +cleaning-stash-cron: "0 0 1 * * *" + diff --git a/src/test/java/org/gridsuite/explore/server/ExploreTest.java b/src/test/java/org/gridsuite/explore/server/ExploreTest.java index 9c8f688e..a69b4d1d 100644 --- a/src/test/java/org/gridsuite/explore/server/ExploreTest.java +++ b/src/test/java/org/gridsuite/explore/server/ExploreTest.java @@ -24,6 +24,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -63,6 +65,8 @@ public class ExploreTest { private static final UUID CASE_UUID = UUID.randomUUID(); private static final UUID NON_EXISTING_CASE_UUID = UUID.randomUUID(); private static final UUID PARENT_DIRECTORY_UUID = UUID.randomUUID(); + + private static final UUID DIRECTORY_UUID = UUID.fromString("3cd7cdc4-5a5d-4970-913e-3e91deeb8c35"); private static final UUID PARENT_DIRECTORY_WITH_ERROR_UUID = UUID.randomUUID(); private static final UUID PRIVATE_STUDY_UUID = UUID.randomUUID(); private static final UUID PUBLIC_STUDY_UUID = UUID.randomUUID(); @@ -107,6 +111,12 @@ public class ExploreTest { private ObjectMapper mapper; private MockWebServer server; + @InjectMocks + private ScheduledCleaner scheduledCleaner; + + @Autowired + private ExploreService exploreService; + @Before public void setup() throws IOException { server = new MockWebServer(); @@ -141,6 +151,8 @@ public void setup() throws IOException { String modificationElementAttributesAsString = mapper.writeValueAsString(new ElementAttributes(MODIFICATION_UUID, "one modif", "MODIFICATION", new AccessRightsAttributes(true), USER1, 0L, null)); String modificationInfosAttributesAsString = mapper.writeValueAsString(List.of(modificationSpecificMetadata)); String modificationIdsAsString = mapper.writeValueAsString(Map.of(MODIFICATION_UUID, MODIFICATION_UUID)); + String directoryAttributesAsString2 = mapper.writeValueAsString(new ElementAttributes(DIRECTORY_UUID, "directory", "DIRECTORY", new AccessRightsAttributes(true), USER1, 0, null)); + String directoryAttributesStashedAsString = mapper.writeValueAsString(List.of(new ElementAttributes(DIRECTORY_UUID, "directory", "CONTINGENCY_LIST", new AccessRightsAttributes(true), USER1, 0, null))); final Dispatcher dispatcher = new Dispatcher() { @SneakyThrows @@ -261,6 +273,8 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/elements/" + PARENT_DIRECTORY_UUID)) { return new MockResponse().setBody(directoryAttributesAsString).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); + } else if (path.matches("/v1/elements/" + DIRECTORY_UUID)) { + return new MockResponse().setBody(directoryAttributesAsString2).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/filters/metadata[?]ids=" + FILTER_UUID)) { return new MockResponse().setBody(listOfFilterAttributesAsString.replace("elementUuid", "id")).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/cases/metadata[?]ids=" + CASE_UUID)) { @@ -270,6 +284,12 @@ public MockResponse dispatch(RecordedRequest request) { } else if (path.matches("/v1/studies/metadata[?]ids=" + PRIVATE_STUDY_UUID)) { return new MockResponse().setBody(listOfPrivateStudyAttributesAsString.replace("elementUuid", "id")).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); + } else if (path.matches("/v1/elements/stashed\\?daysAgo=.*")) { + return new MockResponse().setBody(directoryAttributesStashedAsString).setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8"); + } else if (path.matches("/v1/directories/3cd7cdc4-5a5d-4970-913e-3e91deeb8c35/elements\\?stashed=true")) { + return new MockResponse().setBody(directoryAttributesStashedAsString).setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8"); } } else if ("DELETE".equals(request.getMethod())) { if (path.matches("/v1/filters/" + FILTER_UUID)) { @@ -698,4 +718,20 @@ public void testGetModificationMetadata() { assertEquals(1, elementsMetadata.size()); assertEquals(mapper.writeValueAsString(elementsMetadata.get(0)), expectedResult); } + + @Test + public void testDeleteStashedExpired() { + scheduledCleaner.deleteStashedExpired(); + Mockito.verify(exploreService).deleteStashedElements(30); + } + /* @Test + public void testDeleteStashedElements() { + int daysAgo = 30; + exploreService.deleteStashedElements(daysAgo); + + Map> expectedMap = Map.of(USER1, List.of(DIRECTORY_UUID)); + expectedMap.forEach((userId, uuids) -> + Mockito.verify(exploreService).deleteElements(uuids, userId)); + }*/ + } From c50e625a44d4d7c0900271b3f893bb28f04faeaa Mon Sep 17 00:00:00 2001 From: TOURI ANIS Date: Thu, 4 Apr 2024 15:08:25 +0200 Subject: [PATCH 2/2] Optimize calls and add unit tests Signed-off-by: TOURI ANIS --- .../server/services/DirectoryService.java | 8 +++- .../server/services/ExploreService.java | 37 ++++++++++----- .../server/services/ScheduledCleaner.java | 2 +- .../gridsuite/explore/server/ExploreTest.java | 45 ++++++++++++------- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java b/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java index a8576355..37ccdbb5 100644 --- a/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java +++ b/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java @@ -178,8 +178,12 @@ private List getDirectoryElements(UUID directoryUuid, String public void deleteElement(UUID id, String userId) { ElementAttributes elementAttribute = getElementInfos(id); - IDirectoryElementsService service = getGenericService(elementAttribute.getType()); - service.delete(elementAttribute.getElementUuid(), userId); + deleteElementByElementAttribute(elementAttribute, userId); + } + + public void deleteElementByElementAttribute(ElementAttributes elementAttributes, String userId) { + IDirectoryElementsService service = getGenericService(elementAttributes.getType()); + service.delete(elementAttributes.getElementUuid(), userId); } private IDirectoryElementsService getGenericService(String type) { diff --git a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java index e3a847df..43518093 100644 --- a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java +++ b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java @@ -216,16 +216,33 @@ public void deleteElements(List uuids, String userId) { } } - public void deleteStashedElements(int daysAgo) { - Map> stashedElementsToDelete = directoryService.getStashedElementInfos(daysAgo).stream() - .collect(Collectors.groupingBy( - ElementAttributes::getOwner, - Collectors.mapping( - ElementAttributes::getElementUuid, - Collectors.toList() - ) - )); - stashedElementsToDelete.forEach((userId, uuids) -> deleteElements(uuids, userId)); + public void deleteElementsByElementAttributes(String userId, List elementsAttributes) { + try { + elementsAttributes.forEach(elementAttributes -> + directoryService.deleteElementByElementAttribute(elementAttributes, userId)); + } catch (Exception e) { + LOGGER.error(e.toString(), e); + } finally { + List elementsUuid = elementsAttributes.stream() + .map(ElementAttributes::getElementUuid) + .toList(); + directoryService.deleteDirectoryElements( + elementsUuid, + userId); + } + } + + public void deleteStashedElementsOlderThanDays(int daysAgo) { + Map> stashedElementsToDelete = directoryService.getStashedElementInfos(daysAgo).stream() + .collect(Collectors.groupingBy(ElementAttributes::getOwner)); + if (stashedElementsToDelete.isEmpty()) { + LOGGER.error("No elements found in the trash to clean up."); + return; + } + stashedElementsToDelete.forEach((owner, elementAttributesList) -> + deleteElementsByElementAttributes(owner, elementAttributesList) + ); + LOGGER.error("Trash cleanup completed successfully."); } public void updateFilter(UUID id, String filter, String userId, String name) { diff --git a/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java b/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java index 95597df1..48be96cc 100644 --- a/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java +++ b/src/main/java/org/gridsuite/explore/server/services/ScheduledCleaner.java @@ -22,7 +22,7 @@ public ScheduledCleaner(ExploreService exploreService) { public void deleteStashedExpired() { ZonedDateTime startZonedDateTime = ZonedDateTime.now(ZoneOffset.UTC); LOGGER.info("Cleaning cases cron starting execution at {}", startZonedDateTime); - exploreService.deleteStashedElements(30); // delete all stashed elements older than 30 days + exploreService.deleteStashedElementsOlderThanDays(30); // delete all stashed elements older than 30 days ZonedDateTime endZonedDateTime = ZonedDateTime.now(ZoneOffset.UTC); LOGGER.info("Cleaning cases cron finished execution at {}", endZonedDateTime); } diff --git a/src/test/java/org/gridsuite/explore/server/ExploreTest.java b/src/test/java/org/gridsuite/explore/server/ExploreTest.java index a69b4d1d..e1d9cd28 100644 --- a/src/test/java/org/gridsuite/explore/server/ExploreTest.java +++ b/src/test/java/org/gridsuite/explore/server/ExploreTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -90,11 +91,17 @@ public class ExploreTest { private final Map modificationSpecificMetadata = Map.of("id", MODIFICATION_UUID, "type", "LOAD_MODIFICATION"); private static final UUID SCRIPT_ID_BASE_FORM_CONTINGENCY_LIST_UUID = UUID.randomUUID(); + ElementAttributes contingencyElementAttributes = new ElementAttributes(CONTINGENCY_LIST_UUID, "directory", "CONTINGENCY_LIST", new AccessRightsAttributes(true), USER1, 0, null); @Autowired private MockMvc mockMvc; @Autowired private DirectoryService directoryService; + + @Mock + private DirectoryService directoryServiceMock; + @InjectMocks + private ExploreService exploreServiceMock; @Autowired private ContingencyListService contingencyListService; @Autowired @@ -114,7 +121,7 @@ public class ExploreTest { @InjectMocks private ScheduledCleaner scheduledCleaner; - @Autowired + @Mock private ExploreService exploreService; @Before @@ -152,7 +159,7 @@ public void setup() throws IOException { String modificationInfosAttributesAsString = mapper.writeValueAsString(List.of(modificationSpecificMetadata)); String modificationIdsAsString = mapper.writeValueAsString(Map.of(MODIFICATION_UUID, MODIFICATION_UUID)); String directoryAttributesAsString2 = mapper.writeValueAsString(new ElementAttributes(DIRECTORY_UUID, "directory", "DIRECTORY", new AccessRightsAttributes(true), USER1, 0, null)); - String directoryAttributesStashedAsString = mapper.writeValueAsString(List.of(new ElementAttributes(DIRECTORY_UUID, "directory", "CONTINGENCY_LIST", new AccessRightsAttributes(true), USER1, 0, null))); + String contingencyAttributesStashedAsString = mapper.writeValueAsString(List.of(contingencyElementAttributes)); final Dispatcher dispatcher = new Dispatcher() { @SneakyThrows @@ -285,10 +292,7 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setBody(listOfPrivateStudyAttributesAsString.replace("elementUuid", "id")).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/elements/stashed\\?daysAgo=.*")) { - return new MockResponse().setBody(directoryAttributesStashedAsString).setResponseCode(200) - .addHeader("Content-Type", "application/json; charset=utf-8"); - } else if (path.matches("/v1/directories/3cd7cdc4-5a5d-4970-913e-3e91deeb8c35/elements\\?stashed=true")) { - return new MockResponse().setBody(directoryAttributesStashedAsString).setResponseCode(200) + return new MockResponse().setBody(contingencyAttributesStashedAsString).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); } } else if ("DELETE".equals(request.getMethod())) { @@ -318,7 +322,7 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/parameters/" + PARAMETERS_UUID)) { return new MockResponse().setResponseCode(200); - } else if (path.matches("\\/v1\\/elements\\?ids=([^,]+,){2,}[^,]+$")) { + } else if (path.matches("\\/v1\\/elements\\?ids=([^,]+,?)+$")) { return new MockResponse().setResponseCode(200); } return new MockResponse().setResponseCode(404); @@ -722,16 +726,25 @@ public void testGetModificationMetadata() { @Test public void testDeleteStashedExpired() { scheduledCleaner.deleteStashedExpired(); - Mockito.verify(exploreService).deleteStashedElements(30); + Mockito.verify(exploreService).deleteStashedElementsOlderThanDays(30); } - /* @Test - public void testDeleteStashedElements() { - int daysAgo = 30; - exploreService.deleteStashedElements(daysAgo); - Map> expectedMap = Map.of(USER1, List.of(DIRECTORY_UUID)); - expectedMap.forEach((userId, uuids) -> - Mockito.verify(exploreService).deleteElements(uuids, userId)); - }*/ + @Test + public void testDeleteStashedElementsOlderThanDaysWithNonEmptyList() { + Mockito.when(directoryServiceMock.getStashedElementInfos(Mockito.anyInt())).thenReturn(List.of(contingencyElementAttributes)); + + exploreServiceMock.deleteStashedElementsOlderThanDays(30); + Mockito.verify(directoryServiceMock, Mockito.atLeastOnce()).deleteElementByElementAttribute(Mockito.any(ElementAttributes.class), Mockito.anyString()); + Mockito.verify(directoryServiceMock, Mockito.atLeastOnce()).deleteDirectoryElements(Mockito.anyList(), Mockito.anyString()); + } + + @Test + public void testGetStashedElementInfos() { + List elementAttributes = directoryService.getStashedElementInfos(30); + assertEquals(1, elementAttributes.size()); + assertEquals(CONTINGENCY_LIST_UUID, elementAttributes.get(0).getElementUuid()); + assertEquals(USER1, elementAttributes.get(0).getOwner()); + + } }