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..37ccdbb5 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; @@ -168,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 4f6c4a49..43518093 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,35 @@ public void deleteElements(List uuids, String 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) { 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..48be96cc --- /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.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/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..e1d9cd28 100644 --- a/src/test/java/org/gridsuite/explore/server/ExploreTest.java +++ b/src/test/java/org/gridsuite/explore/server/ExploreTest.java @@ -24,6 +24,9 @@ import org.junit.Before; 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; import org.springframework.boot.test.context.SpringBootTest; @@ -63,6 +66,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(); @@ -86,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 @@ -107,6 +118,12 @@ public class ExploreTest { private ObjectMapper mapper; private MockWebServer server; + @InjectMocks + private ScheduledCleaner scheduledCleaner; + + @Mock + private ExploreService exploreService; + @Before public void setup() throws IOException { server = new MockWebServer(); @@ -141,6 +158,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 contingencyAttributesStashedAsString = mapper.writeValueAsString(List.of(contingencyElementAttributes)); final Dispatcher dispatcher = new Dispatcher() { @SneakyThrows @@ -261,6 +280,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 +291,9 @@ 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(contingencyAttributesStashedAsString).setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8"); } } else if ("DELETE".equals(request.getMethod())) { if (path.matches("/v1/filters/" + FILTER_UUID)) { @@ -298,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); @@ -698,4 +722,29 @@ public void testGetModificationMetadata() { assertEquals(1, elementsMetadata.size()); assertEquals(mapper.writeValueAsString(elementsMetadata.get(0)), expectedResult); } + + @Test + public void testDeleteStashedExpired() { + scheduledCleaner.deleteStashedExpired(); + Mockito.verify(exploreService).deleteStashedElementsOlderThanDays(30); + } + + @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()); + + } }