diff --git a/src/main/java/org/gridsuite/directory/server/DirectoryController.java b/src/main/java/org/gridsuite/directory/server/DirectoryController.java index 467f068f..c1194f63 100644 --- a/src/main/java/org/gridsuite/directory/server/DirectoryController.java +++ b/src/main/java/org/gridsuite/directory/server/DirectoryController.java @@ -206,7 +206,7 @@ public ResponseEntity updateElement(@PathVariable("elementUuid") UUID elem @PutMapping(value = "/elements", params = "targetDirectoryUuid") @Operation(summary = "Move elements within directory tree") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Elements was successfully updated"), + @ApiResponse(responseCode = "200", description = "Elements was successfully moved"), @ApiResponse(responseCode = "404", description = "The elements or the targeted directory was not found"), @ApiResponse(responseCode = "403", description = "Not authorized execute this update") }) diff --git a/src/main/java/org/gridsuite/directory/server/DirectoryException.java b/src/main/java/org/gridsuite/directory/server/DirectoryException.java index 437bbb06..e98ef53d 100644 --- a/src/main/java/org/gridsuite/directory/server/DirectoryException.java +++ b/src/main/java/org/gridsuite/directory/server/DirectoryException.java @@ -50,6 +50,7 @@ public enum Type { NOT_DIRECTORY, IS_DIRECTORY, UNKNOWN_NOTIFICATION, - NAME_ALREADY_EXISTS + NAME_ALREADY_EXISTS, + MOVE_IN_DESCENDANT_NOT_ALLOWED, } } diff --git a/src/main/java/org/gridsuite/directory/server/DirectoryService.java b/src/main/java/org/gridsuite/directory/server/DirectoryService.java index 299a2e5f..95a2ac6e 100644 --- a/src/main/java/org/gridsuite/directory/server/DirectoryService.java +++ b/src/main/java/org/gridsuite/directory/server/DirectoryService.java @@ -30,6 +30,7 @@ import java.time.temporal.ChronoUnit; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.gridsuite.directory.server.DirectoryException.Type.*; @@ -327,26 +328,43 @@ public void moveElementsDirectory(List elementsUuids, UUID newDirectoryUui validateNewDirectory(newDirectoryUuid); - elementsUuids.forEach(elementUuid -> moveElementDirectory(elementUuid, newDirectoryUuid, userId)); + elementsUuids.forEach(elementUuid -> moveElementDirectory(getDirectoryElementEntity(elementUuid), newDirectoryUuid, userId)); } - private void moveElementDirectory(UUID elementUuid, UUID newDirectoryUuid, String userId) { - DirectoryElementEntity element = repositoryService.getElementEntity(elementUuid) - .orElseThrow(() -> DirectoryException.createElementNotFound(ELEMENT, elementUuid)); + private void moveElementDirectory(DirectoryElementEntity element, UUID newDirectoryUuid, String userId) { + if (Objects.equals(element.getParentId(), newDirectoryUuid)) { // Same directory ? + return; + } + + DirectoryElementEntity oldDirectory = element.getParentId() == null ? null : repositoryService.getElementEntity(element.getParentId()).orElseThrow(); + boolean isDirectory = DIRECTORY.equals(element.getType()); + List descendents = isDirectory ? repositoryService.findAllDescendants(element.getId()).stream().toList() : List.of(); - validateElementForMove(element, newDirectoryUuid, userId); + // validate move elements + validateElementForMove(element, newDirectoryUuid, userId, descendents.stream().map(DirectoryElementEntity::getId).collect(Collectors.toSet())); - DirectoryElementEntity oldDirectory = repositoryService.getElementEntity(element.getParentId()).orElseThrow(); + // we update the parent of the moving element updateElementParentDirectory(element, newDirectoryUuid); - notifyDirectoryHasChanged(element.getParentId() == null ? element.getId() : element.getParentId(), userId, element.getName()); - notifyDirectoryHasChanged(oldDirectory.getId(), userId, element.getName()); + + // reindex descendents + repositoryService.reindexElements(descendents); + + // if it has a parent, we notify it. + // otherwise, which means it is a root, we send a notification that a root has been deleted (in this case, it moved under a new directory) + if (oldDirectory != null) { + notifyDirectoryHasChanged(oldDirectory.getId(), userId, element.getName(), isDirectory); + } else { + notifyRootDirectoryDeleted(element.getId(), userId, element.getName(), isDirectory); + } + notifyDirectoryHasChanged(newDirectoryUuid, userId, element.getName(), isDirectory); } - private void validateElementForMove(DirectoryElementEntity element, UUID newDirectoryUuid, String userId) { - if (element.getType().equals(DIRECTORY)) { - throw new DirectoryException(IS_DIRECTORY); + private void validateElementForMove(DirectoryElementEntity element, UUID newDirectoryUuid, String userId, Set descendentsUuids) { + if (newDirectoryUuid == element.getId() || descendentsUuids.contains(newDirectoryUuid)) { + throw new DirectoryException(MOVE_IN_DESCENDANT_NOT_ALLOWED); } + if (!isDirectoryElementUpdatable(toElementAttributes(element), userId) || directoryHasElementOfNameAndType(newDirectoryUuid, element.getName(), element.getType())) { throw new DirectoryException(NOT_ALLOWED); @@ -455,7 +473,7 @@ public void deleteElements(List elementsUuids, UUID parentDirectoryUuid, S * @return ElementAttributes of element and all it's parents up to root directory */ public List getPath(UUID elementUuid) { - List path = repositoryService.findElementHierarchy(elementUuid).stream().map(ElementAttributes::toElementAttributes).toList(); + List path = repositoryService.getPath(elementUuid).stream().map(ElementAttributes::toElementAttributes).toList(); if (path.isEmpty()) { throw DirectoryException.createElementNotFound(ELEMENT, elementUuid); } @@ -580,15 +598,23 @@ public UUID getDirectoryUuidFromPath(List directoryPath) { // then we send a notification on this directory private void notifyDirectoryHasChanged(UUID directoryUuid, String userId) { Objects.requireNonNull(directoryUuid); - notifyDirectoryHasChanged(directoryUuid, userId, null, null); + notifyDirectoryHasChanged(directoryUuid, userId, null, null, false); } private void notifyDirectoryHasChanged(UUID directoryUuid, String userId, String elementName) { + notifyDirectoryHasChanged(directoryUuid, userId, elementName, false); + } + + private void notifyDirectoryHasChanged(UUID directoryUuid, String userId, String elementName, boolean isDirectoryMoving) { Objects.requireNonNull(directoryUuid); - notifyDirectoryHasChanged(directoryUuid, userId, elementName, null); + notifyDirectoryHasChanged(directoryUuid, userId, elementName, null, isDirectoryMoving); } private void notifyDirectoryHasChanged(UUID directoryUuid, String userId, String elementName, String error) { + notifyDirectoryHasChanged(directoryUuid, userId, elementName, error, false); + } + + private void notifyDirectoryHasChanged(UUID directoryUuid, String userId, String elementName, String error, boolean isDirectoryMoving) { Objects.requireNonNull(directoryUuid); notificationService.emitDirectoryChanged( directoryUuid, @@ -596,6 +622,7 @@ private void notifyDirectoryHasChanged(UUID directoryUuid, String userId, String userId, error, repositoryService.isRootDirectory(directoryUuid), + isDirectoryMoving, NotificationType.UPDATE_DIRECTORY ); } @@ -603,10 +630,15 @@ private void notifyDirectoryHasChanged(UUID directoryUuid, String userId, String // Root directories don't have parent directories. Then if on is deleted, we must send a specific notification private void notifyRootDirectoryDeleted(UUID rootDirectoryUuid, String userId, String elementName) { Objects.requireNonNull(rootDirectoryUuid); - notifyRootDirectoryDeleted(rootDirectoryUuid, userId, elementName, null); + notifyRootDirectoryDeleted(rootDirectoryUuid, userId, elementName, false); + } + + private void notifyRootDirectoryDeleted(UUID rootDirectoryUuid, String userId, String elementName, boolean isDirectoryMoving) { + Objects.requireNonNull(rootDirectoryUuid); + notifyRootDirectoryDeleted(rootDirectoryUuid, userId, elementName, null, isDirectoryMoving); } - private void notifyRootDirectoryDeleted(UUID rootDirectoryUuid, String userId, String elementName, String error) { + private void notifyRootDirectoryDeleted(UUID rootDirectoryUuid, String userId, String elementName, String error, boolean isDirectoryMoving) { Objects.requireNonNull(rootDirectoryUuid); notificationService.emitDirectoryChanged( rootDirectoryUuid, @@ -614,6 +646,7 @@ private void notifyRootDirectoryDeleted(UUID rootDirectoryUuid, String userId, S userId, error, true, + isDirectoryMoving, NotificationType.DELETE_DIRECTORY ); } diff --git a/src/main/java/org/gridsuite/directory/server/NotificationService.java b/src/main/java/org/gridsuite/directory/server/NotificationService.java index 5b027273..bf574cc6 100644 --- a/src/main/java/org/gridsuite/directory/server/NotificationService.java +++ b/src/main/java/org/gridsuite/directory/server/NotificationService.java @@ -35,6 +35,7 @@ public class NotificationService { public static final String HEADER_NOTIFICATION_TYPE = "notificationType"; public static final String HEADER_ELEMENT_NAME = "elementName"; public static final String HEADER_ELEMENT_UUID = "elementUuid"; + public static final String HEADER_IS_DIRECTORY_MOVING = "isDirectoryMoving"; public static final String UPDATE_TYPE_ELEMENT_DELETE = "deleteElement"; private static final String CATEGORY_BROKER_OUTPUT = DirectoryService.class.getName() + ".output-broker-messages"; private static final Logger MESSAGE_OUTPUT_LOGGER = LoggerFactory.getLogger(CATEGORY_BROKER_OUTPUT); @@ -48,6 +49,10 @@ private void sendUpdateMessage(Message message) { } public void emitDirectoryChanged(UUID directoryUuid, String elementName, String userId, String error, boolean isRoot, NotificationType notificationType) { + emitDirectoryChanged(directoryUuid, elementName, userId, error, isRoot, false, notificationType); + } + + public void emitDirectoryChanged(UUID directoryUuid, String elementName, String userId, String error, boolean isRoot, boolean isDirectoryMoving, NotificationType notificationType) { MessageBuilder messageBuilder = MessageBuilder.withPayload("") .setHeader(HEADER_USER_ID, userId) .setHeader(HEADER_DIRECTORY_UUID, directoryUuid) @@ -56,6 +61,7 @@ public void emitDirectoryChanged(UUID directoryUuid, String elementName, String .setHeader(HEADER_IS_PUBLIC_DIRECTORY, true) // null may only come from borked REST request .setHeader(HEADER_NOTIFICATION_TYPE, notificationType) .setHeader(HEADER_UPDATE_TYPE, UPDATE_TYPE_DIRECTORIES) + .setHeader(HEADER_IS_DIRECTORY_MOVING, isDirectoryMoving) .setHeader(HEADER_ERROR, error); sendUpdateMessage(messageBuilder.build()); } diff --git a/src/main/java/org/gridsuite/directory/server/RestResponseEntityExceptionHandler.java b/src/main/java/org/gridsuite/directory/server/RestResponseEntityExceptionHandler.java index c676c523..f10d7329 100644 --- a/src/main/java/org/gridsuite/directory/server/RestResponseEntityExceptionHandler.java +++ b/src/main/java/org/gridsuite/directory/server/RestResponseEntityExceptionHandler.java @@ -42,6 +42,8 @@ protected ResponseEntity handleException(RuntimeException exception) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(UNKNOWN_NOTIFICATION); case NAME_ALREADY_EXISTS: return ResponseEntity.status(HttpStatus.CONFLICT).body(directoryException.getMessage()); + case MOVE_IN_DESCENDANT_NOT_ALLOWED: + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(MOVE_IN_DESCENDANT_NOT_ALLOWED); default: return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } diff --git a/src/main/java/org/gridsuite/directory/server/repository/DirectoryElementRepository.java b/src/main/java/org/gridsuite/directory/server/repository/DirectoryElementRepository.java index 943629d9..3b0e1c69 100644 --- a/src/main/java/org/gridsuite/directory/server/repository/DirectoryElementRepository.java +++ b/src/main/java/org/gridsuite/directory/server/repository/DirectoryElementRepository.java @@ -88,20 +88,15 @@ interface SubDirectoryCount { List findElementHierarchy(@Param("elementId") UUID elementId); @Query(nativeQuery = true, value = - "WITH RECURSIVE DescendantHierarchy (element_id, parent_element_id) AS (" + - " SELECT" + - " id AS element_id, parent_id AS parent_element_id" + - " FROM element where id = :elementId" + + "WITH RECURSIVE DescendantHierarchy (element_id, parent_element_id, depth) AS (" + + " SELECT id AS element_id, parent_id AS parent_element_id, 0 AS depth" + + " FROM element WHERE id = :elementId" + " UNION ALL" + - " select e.id AS element_id, e.parent_id AS parent_element_id" + + " SELECT e.id AS element_id, e.parent_id AS parent_element_id, dh.depth + 1" + " FROM element e" + - " INNER JOIN" + - " DescendantHierarchy dh" + - " ON dh.element_id = e.parent_id" + - " WHERE e.type = 'DIRECTORY' )" + - "SELECT * FROM element e" + - "JOIN" + - " DescendantHierarchy dh" + - " ON e.id = dh.element_id") + " INNER JOIN DescendantHierarchy dh ON dh.element_id = e.parent_id)" + + "SELECT * FROM element e " + + "WHERE e.id IN (SELECT dh.element_id FROM DescendantHierarchy dh) AND e.id != :elementId" + ) List findAllDescendants(@Param("elementId") UUID elementId); } diff --git a/src/main/java/org/gridsuite/directory/server/services/DirectoryRepositoryService.java b/src/main/java/org/gridsuite/directory/server/services/DirectoryRepositoryService.java index 4e12b5e4..cbfb71dc 100644 --- a/src/main/java/org/gridsuite/directory/server/services/DirectoryRepositoryService.java +++ b/src/main/java/org/gridsuite/directory/server/services/DirectoryRepositoryService.java @@ -15,9 +15,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import static org.gridsuite.directory.server.DirectoryService.DIRECTORY; @@ -59,19 +57,23 @@ public boolean isElementExists(UUID parentDirectoryUuid, String elementName, Str return !directoryElementRepository.findByNameAndParentIdAndType(elementName, parentDirectoryUuid, type).isEmpty(); } - public void saveElementsInfos(@NonNull List directoryElementInfos) { + private void saveElementsInfos(List directoryElements) { + Map> pathsCache = new HashMap<>(); + List directoryElementInfos = directoryElements.stream() + .map(directoryElementEntity -> directoryElementEntity.toDirectoryElementInfos(getPath(directoryElementEntity.getParentId(), pathsCache))) + .toList(); Lists.partition(directoryElementInfos, partitionSize) .parallelStream() .forEach(directoryElementInfosRepository::saveAll); } - private DirectoryElementEntity saveElementsInfo(DirectoryElementEntity elementEntity) { - directoryElementInfosRepository.save(elementEntity.toDirectoryElementInfos(findElementHierarchy(elementEntity.getParentId()))); + private DirectoryElementEntity saveElementInfos(DirectoryElementEntity elementEntity) { + directoryElementInfosRepository.save(elementEntity.toDirectoryElementInfos(getPath(elementEntity.getParentId()))); return elementEntity; } public DirectoryElementEntity saveElement(DirectoryElementEntity elementEntity) { - return saveElementsInfo(directoryElementRepository.save(elementEntity)); + return saveElementInfos(directoryElementRepository.save(elementEntity)); } public void deleteElement(UUID elementUuid) { @@ -89,9 +91,11 @@ public boolean canRead(UUID id, String userId) { } public void reindexElements() { - saveElementsInfos(directoryElementRepository.findAll().stream() - .map(directoryElementEntity -> directoryElementEntity.toDirectoryElementInfos(findElementHierarchy(directoryElementEntity.getParentId()))) - .toList()); + reindexElements(directoryElementRepository.findAll()); + } + + public void reindexElements(@NonNull List elementEntities) { + saveElementsInfos(elementEntities); } public UUID getParentUuid(UUID elementUuid) { @@ -129,9 +133,16 @@ public List getNameByTypeAndParentIdAndNameStartWith(String type, UUID p return directoryElementRepository.getNameByTypeAndParentIdAndNameStartWith(type, parentId, name); } - public List findElementHierarchy(UUID elementId) { + public List getPath(UUID elementId) { + return getPath(elementId, new HashMap<>()); + } + + public List getPath(UUID elementId, Map> pathsCache) { // Test null for root directories - return elementId == null ? List.of() : directoryElementRepository.findElementHierarchy(elementId); + return elementId == null ? List.of() : pathsCache.computeIfAbsent(elementId, directoryElementRepository::findElementHierarchy); } + public List findAllDescendants(@NonNull UUID elementId) { + return directoryElementRepository.findAllDescendants(elementId); + } } diff --git a/src/test/java/org/gridsuite/directory/server/DirectoryElementInfosServiceTest.java b/src/test/java/org/gridsuite/directory/server/DirectoryElementInfosServiceTest.java index 79ed1eb9..6d653f44 100644 --- a/src/test/java/org/gridsuite/directory/server/DirectoryElementInfosServiceTest.java +++ b/src/test/java/org/gridsuite/directory/server/DirectoryElementInfosServiceTest.java @@ -11,7 +11,6 @@ import org.gridsuite.directory.server.dto.elasticsearch.DirectoryElementInfos; import org.gridsuite.directory.server.elasticsearch.DirectoryElementInfosRepository; import org.gridsuite.directory.server.services.DirectoryElementInfosService; -import org.gridsuite.directory.server.services.DirectoryRepositoryService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,8 +38,6 @@ class DirectoryElementInfosServiceTest { public static final String TYPE_03 = "TYPE_03"; public static final String TYPE_04 = "TYPE_04"; public static final String DIRECTORY = "DIRECTORY"; - @Autowired - DirectoryRepositoryService repositoryService; @Autowired DirectoryElementInfosRepository directoryElementInfosRepository; @@ -66,7 +63,7 @@ void testAddDeleteElementInfos() { // Add List infos = List.of(element1Infos, element2Infos, directoryInfos, element3Infos); - repositoryService.saveElementsInfos(infos); + directoryElementInfosRepository.saveAll(infos); List infosDB = IterableUtils.toList(directoryElementInfosRepository.findAll()); assertEquals(4, infosDB.size()); assertEquals(infos, infosDB); @@ -90,7 +87,7 @@ void searchElementInfos() { var element3Infos = DirectoryElementInfos.builder().id(UUID.randomUUID()).name("elementName3").type(TYPE_03).owner("admin").parentId(UUID.randomUUID()).subdirectoriesCount(0L).lastModificationDate(Instant.now().truncatedTo(ChronoUnit.SECONDS)).build(); List infos = List.of(directoryInfos, element2Infos, element1Infos, element4Infos, element3Infos); - repositoryService.saveElementsInfos(infos); + directoryElementInfosRepository.saveAll(infos); Set hits = new HashSet<>(directoryElementInfosService.searchElements("a", "", PageRequest.of(0, 10)).stream().toList()); assertEquals(4, hits.size()); @@ -115,7 +112,7 @@ void searchPagedElementInfos() { for (int i = 0; i < 20; i++) { elements.add(createElements("filter" + i)); } - repositoryService.saveElementsInfos(elements); + directoryElementInfosRepository.saveAll(elements); Page pagedHits = directoryElementInfosService.searchElements("filter", "", PageRequest.of(0, 10)); assertEquals(20, pagedHits.getTotalElements()); assertEquals(10, pagedHits.getContent().size()); @@ -126,7 +123,7 @@ void searchSpecialChars() { var studyInfos = DirectoryElementInfos.builder().id(UUID.randomUUID()).name("s+Ss+ss'sp&pn(n n)ne{e e}ette|eh-ht.th/hl\\lk[k k]k") .type(TYPE_01).owner("admin1").parentId(UUID.randomUUID()) .subdirectoriesCount(0L).lastModificationDate(Instant.now().truncatedTo(ChronoUnit.SECONDS)).build(); - repositoryService.saveElementsInfos(List.of(studyInfos)); + directoryElementInfosRepository.saveAll(List.of(studyInfos)); testNameFullAscii("s+S"); testNameFullAscii("s+s"); @@ -209,12 +206,12 @@ HashMap createFilesElements() { var commonFile5 = makeElementFile("common_file", allDirs.get("sub_sub_directory3_1").getId()); var commonFile6 = makeElementFile("common_file", allDirs.get("sub_sub_directory3_2").getId()); - repositoryService.saveElementsInfos(allDirs.values().stream().toList()); + directoryElementInfosRepository.saveAll(allDirs.values().stream().toList()); List infos = List.of( file1, file2, file3, commonFile1, commonFile2, commonFile3, commonFile4, commonFile5, commonFile6); - repositoryService.saveElementsInfos(infos); + directoryElementInfosRepository.saveAll(infos); return allDirs; } @@ -267,7 +264,7 @@ void testExactMatchingParentDirectory() { // when a file is in a sub directory o var newFile1 = makeElementFile("new-file", allDirs.get("sub_directory2").getId()); var newFile2 = makeElementFile("new-file", allDirs.get("sub_sub_directory2_2").getId()); var newFile3 = makeElementFile("new-file", allDirs.get("sub_sub_directory3_1").getId()); - repositoryService.saveElementsInfos(List.of(newFile1, newFile2, newFile3)); + directoryElementInfosRepository.saveAll(List.of(newFile1, newFile2, newFile3)); //we want to have the files in the current directory if any // then the files in the path of the current directory (sub directories and parent directories) @@ -298,7 +295,7 @@ void testExactMatchInCurrentDir() { var newFile = makeElementFile(fileName, allDirs.get("sub_sub_directory1_2").getId()); var newFile1 = makeElementFile(fileName + "1", allDirs.get("sub_sub_directory1_2").getId()); var newFile2 = makeElementFile("1" + fileName + "2", allDirs.get("sub_sub_directory1_2").getId()); - repositoryService.saveElementsInfos(List.of(newFile, newFile2, newFile1)); + directoryElementInfosRepository.saveAll(List.of(newFile, newFile2, newFile1)); List hitsFile = directoryElementInfosService.searchElements(fileName, currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList(); assertEquals(3, hitsFile.size()); assertEquals(fileName, hitsFile.get(0).getName()); @@ -325,7 +322,7 @@ void testTermStartByUserInput() { // when a file start with search term var newFile2 = makeElementFile("new-file-Ok", allDirs.get("sub_directory2").getId()); var bNewFile = makeElementFile("bnew-filebbbb", allDirs.get("sub_directory2").getId()); var testNewFile = makeElementFile("test-new-file", allDirs.get("sub_directory2").getId()); - repositoryService.saveElementsInfos(List.of(bNewFile, newFile2, anewFile1, testNewFile)); + directoryElementInfosRepository.saveAll(List.of(bNewFile, newFile2, anewFile1, testNewFile)); //we want to have the files in the current directory if any // then the files in the path of the current directory (sub directories and parent directories) diff --git a/src/test/java/org/gridsuite/directory/server/DirectoryServiceTest.java b/src/test/java/org/gridsuite/directory/server/DirectoryServiceTest.java index cb7f1f2d..8bcc7c63 100644 --- a/src/test/java/org/gridsuite/directory/server/DirectoryServiceTest.java +++ b/src/test/java/org/gridsuite/directory/server/DirectoryServiceTest.java @@ -102,7 +102,7 @@ void testDeleteMultipleElementsFromOneDirectory() { verify(notificationService, times(1)).emitDeletedElement(element2.getId(), "user1"); verify(notificationService, times(1)).emitDeletedElement(element0.getId(), "user1"); // notification for updated directory - verify(notificationService, times(1)).emitDirectoryChanged(parentDirectoryUuid, null, "user1", null, true, NotificationType.UPDATE_DIRECTORY); + verify(notificationService, times(1)).emitDirectoryChanged(parentDirectoryUuid, null, "user1", null, true, false, NotificationType.UPDATE_DIRECTORY); verifyNoMoreInteractions(notificationService); } @@ -155,4 +155,96 @@ void testDirectoryElementUniqueness() { assertEquals(DirectoryException.createElementNameAlreadyExists(elementAttributes.getElementName()).getMessage(), directoryException.getMessage()); inOrder.verify(directoryService, calls(MAX_RETRY)).getDuplicateNameCandidate(root2Uuid, elementAttributes.getElementName(), elementAttributes.getType(), "User1"); } + + @Test + void testMoveElement() { + // Create root + ElementAttributes rootAttributes = directoryService.createRootDirectory(new RootDirectoryAttributes("root", "user1", null, null, null, null), "user1"); + UUID rootUuid = rootAttributes.getElementUuid(); + + ElementAttributes root2Attributes = directoryService.createRootDirectory(new RootDirectoryAttributes("root2", "user1", null, null, null, null), "user1"); + UUID root2Uuid = root2Attributes.getElementUuid(); + + // Insert elements + ElementAttributes directoryElementAttributes = toElementAttributes(null, "dir", DIRECTORY, "user1"); + UUID dirUuid = directoryService.createElement(directoryElementAttributes, rootUuid, "user1", false).getElementUuid(); + + ElementAttributes subDirectoryElementAttributes = toElementAttributes(null, "subDir", DIRECTORY, "user1"); + UUID subDirUuid = directoryService.createElement(subDirectoryElementAttributes, dirUuid, "user1", false).getElementUuid(); + + ElementAttributes elementAttributes1 = toElementAttributes(null, "element1", "TYPE1", "user1"); + UUID elementUuid1 = directoryService.createElement(elementAttributes1, subDirUuid, "user1", false).getElementUuid(); + + ElementAttributes elementAttributes2 = toElementAttributes(null, "element2", "TYPE2", "user1"); + UUID elementUuid2 = directoryService.createElement(elementAttributes2, subDirUuid, "user1", false).getElementUuid(); + + ElementAttributes elementAttributes3 = toElementAttributes(null, "element3", "TYPE3", "user1"); + UUID elementUuid3 = directoryService.createElement(elementAttributes3, subDirUuid, "user1", false).getElementUuid(); + + verify(notificationService, times(1)).emitDirectoryChanged(subDirUuid, "element1", "user1", null, false, false, NotificationType.UPDATE_DIRECTORY); + verify(notificationService, times(1)).emitDirectoryChanged(subDirUuid, "element2", "user1", null, false, false, NotificationType.UPDATE_DIRECTORY); + + // we move element1 and element2 from subDir to dir + reset(directoryElementRepository); + directoryService.moveElementsDirectory(List.of(elementUuid1, elementUuid2), dirUuid, "user1"); + verify(directoryElementRepository, times(2)).findElementHierarchy(any(UUID.class)); + + verify(notificationService, times(2)).emitDirectoryChanged(subDirUuid, "element1", "user1", null, false, false, NotificationType.UPDATE_DIRECTORY); + verify(notificationService, times(1)).emitDirectoryChanged(dirUuid, "element1", "user1", null, false, false, NotificationType.UPDATE_DIRECTORY); + verify(notificationService, times(2)).emitDirectoryChanged(subDirUuid, "element2", "user1", null, false, false, NotificationType.UPDATE_DIRECTORY); + verify(notificationService, times(1)).emitDirectoryChanged(dirUuid, "element2", "user1", null, false, false, NotificationType.UPDATE_DIRECTORY); + + Optional elementEntity1 = directoryElementRepository.findById(elementUuid1); + Optional elementEntity2 = directoryElementRepository.findById(elementUuid2); + Optional elementEntity3 = directoryElementRepository.findById(elementUuid3); + assertTrue(elementEntity1.isPresent()); + assertTrue(elementEntity2.isPresent()); + assertTrue(elementEntity3.isPresent()); + assertEquals(dirUuid, elementEntity1.get().getParentId()); + assertEquals(dirUuid, elementEntity2.get().getParentId()); + assertEquals(subDirUuid, elementEntity3.get().getParentId()); + + // we move dir to root2 + reset(directoryElementRepository); + directoryService.moveElementsDirectory(List.of(dirUuid), root2Uuid, "user1"); + verify(directoryElementRepository, times(3)).findElementHierarchy(any(UUID.class)); + verify(notificationService, times(1)).emitDirectoryChanged(rootUuid, "dir", "user1", null, true, true, NotificationType.UPDATE_DIRECTORY); + verify(notificationService, times(1)).emitDirectoryChanged(root2Uuid, "dir", "user1", null, true, true, NotificationType.UPDATE_DIRECTORY); + Optional dirEntity = directoryElementRepository.findById(dirUuid); + assertTrue(dirEntity.isPresent()); + assertEquals(root2Uuid, dirEntity.get().getParentId()); + + // move directory to it's descendent + List list = List.of(dirUuid); // Just for Sonar issue (assertThrows) + DirectoryException exception1 = assertThrows(DirectoryException.class, () -> directoryService.moveElementsDirectory(list, subDirUuid, "user1")); + assertEquals(DirectoryException.Type.MOVE_IN_DESCENDANT_NOT_ALLOWED, exception1.getType()); + } + + @Test + void testMoveInNotExistingDirectory() { + ElementAttributes rootAttributes = directoryService.createRootDirectory(new RootDirectoryAttributes("root", "user1", null, null, null, null), "user1"); + UUID rootUuid = rootAttributes.getElementUuid(); + ElementAttributes elementAttributes = toElementAttributes(null, "element1", "TYPE1", "user1"); + UUID elementUuid = directoryService.createElement(elementAttributes, rootUuid, "user1", false).getElementUuid(); + + UUID randomUuid = UUID.randomUUID(); + List list = List.of(elementUuid); // Just for Sonar issue (assertThrows) + DirectoryException exception3 = assertThrows(DirectoryException.class, () -> directoryService.moveElementsDirectory(list, randomUuid, "user1")); + assertEquals(DirectoryException.createElementNotFound(DIRECTORY, randomUuid).getMessage(), exception3.getMessage()); + } + + @Test + void testMoveInNotDirectory() { + ElementAttributes rootAttributes = directoryService.createRootDirectory(new RootDirectoryAttributes("root", "user1", null, null, null, null), "user1"); + UUID rootUuid = rootAttributes.getElementUuid(); + + ElementAttributes elementAttributes1 = toElementAttributes(null, "element1", "TYPE1", "user1"); + UUID elementUuid1 = directoryService.createElement(elementAttributes1, rootUuid, "user1", false).getElementUuid(); + ElementAttributes elementAttributes2 = toElementAttributes(null, "element2", "TYPE2", "user1"); + UUID elementUuid2 = directoryService.createElement(elementAttributes2, rootUuid, "user1", false).getElementUuid(); + + List list = List.of(elementUuid1); // Just for Sonar issue (assertThrows) + DirectoryException exception2 = assertThrows(DirectoryException.class, () -> directoryService.moveElementsDirectory(list, elementUuid2, "user1")); + assertEquals(DirectoryException.Type.NOT_DIRECTORY, exception2.getType()); + } } diff --git a/src/test/java/org/gridsuite/directory/server/DirectoryTest.java b/src/test/java/org/gridsuite/directory/server/DirectoryTest.java index 3ffe78cf..24385b43 100644 --- a/src/test/java/org/gridsuite/directory/server/DirectoryTest.java +++ b/src/test/java/org/gridsuite/directory/server/DirectoryTest.java @@ -382,8 +382,8 @@ public void testMoveElement() throws Exception { assertEquals("", new String(message.getPayload())); MessageHeaders headers = message.getHeaders(); assertEquals("Doe", headers.get(HEADER_USER_ID)); - assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); - assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(directory21UUID, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(false, headers.get(HEADER_IS_ROOT_DIRECTORY)); assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); @@ -393,8 +393,8 @@ public void testMoveElement() throws Exception { assertEquals("", new String(message.getPayload())); headers = message.getHeaders(); assertEquals("Doe", headers.get(HEADER_USER_ID)); - assertEquals(directory21UUID, headers.get(HEADER_DIRECTORY_UUID)); - assertEquals(false, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); @@ -444,8 +444,8 @@ public void testMoveElementFromDifferentAccessRightsFolder() throws Exception { assertEquals("", new String(message.getPayload())); MessageHeaders headers = message.getHeaders(); assertEquals("Doe", headers.get(HEADER_USER_ID)); - assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); - assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(directory21PrivateUUID, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(false, headers.get(HEADER_IS_ROOT_DIRECTORY)); assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); @@ -454,8 +454,8 @@ public void testMoveElementFromDifferentAccessRightsFolder() throws Exception { assertEquals("", new String(message.getPayload())); headers = message.getHeaders(); assertEquals("Doe", headers.get(HEADER_USER_ID)); - assertEquals(directory21PrivateUUID, headers.get(HEADER_DIRECTORY_UUID)); - assertEquals(false, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); @@ -582,14 +582,51 @@ public void testMoveDirectory() throws Exception { UUID directory21UUID = UUID.randomUUID(); ElementAttributes directory20Attributes = toElementAttributes(directory21UUID, "directory20", DIRECTORY, "Doe"); insertAndCheckSubElementInRootDir(rootDir20Uuid, directory20Attributes); + ElementAttributes elementAttributes1 = toElementAttributes(UUID.randomUUID(), "element1", TYPE_01, "Doe"); + ElementAttributes elementAttributes2 = toElementAttributes(UUID.randomUUID(), "element2", TYPE_02, "Doe"); + insertAndCheckSubElement(directory21UUID, elementAttributes1); + insertAndCheckSubElement(directory21UUID, elementAttributes2); - mockMvc.perform(put("/v1/elements?targetDirectoryUuid=" + rootDir10Uuid) + // test move directory + moveDirectoryAndCheck(directory21UUID, rootDir20Uuid, rootDir10Uuid, false); + + // test move root directory + moveDirectoryAndCheck(rootDir20Uuid, null, rootDir10Uuid, true); + + assertNbElementsInRepositories(5); + } + + private void moveDirectoryAndCheck(UUID directoryUuid, + UUID parentDirectoryUuid, + UUID targetDirectoryUuid, + boolean isMovingDirectoryRoot) throws Exception { + mockMvc.perform(put("/v1/elements?targetDirectoryUuid=" + targetDirectoryUuid) .header("userId", "Doe") .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(List.of(directory21UUID)))) - .andExpect(status().isForbidden()); + .content(objectMapper.writeValueAsString(List.of(directoryUuid)))) + .andExpect(status().isOk()); - assertNbElementsInRepositories(3); + Message message = output.receive(TIMEOUT, directoryUpdateDestination); + assertEquals("", new String(message.getPayload())); + MessageHeaders headers = message.getHeaders(); + assertEquals("Doe", headers.get(HEADER_USER_ID)); + assertEquals(isMovingDirectoryRoot ? directoryUuid : parentDirectoryUuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); + assertEquals(true, headers.get(HEADER_IS_DIRECTORY_MOVING)); + assertEquals(isMovingDirectoryRoot ? NotificationType.DELETE_DIRECTORY : NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); + assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); + + message = output.receive(TIMEOUT, directoryUpdateDestination); + assertEquals("", new String(message.getPayload())); + headers = message.getHeaders(); + assertEquals("Doe", headers.get(HEADER_USER_ID)); + assertEquals(targetDirectoryUuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); + assertEquals(true, headers.get(HEADER_IS_DIRECTORY_MOVING)); + assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); + assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); } @Test @@ -618,9 +655,10 @@ public void testElementMove() throws Exception { assertEquals("", new String(message.getPayload())); MessageHeaders headers = message.getHeaders(); assertEquals("Doe", headers.get(HEADER_USER_ID)); - assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(rootDir20Uuid, headers.get(HEADER_DIRECTORY_UUID)); assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); + assertEquals(false, headers.get(HEADER_IS_DIRECTORY_MOVING)); assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); @@ -628,11 +666,59 @@ public void testElementMove() throws Exception { assertEquals("", new String(message.getPayload())); headers = message.getHeaders(); assertEquals("Doe", headers.get(HEADER_USER_ID)); - assertEquals(rootDir20Uuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); + assertEquals(false, headers.get(HEADER_IS_DIRECTORY_MOVING)); assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); + + // Test move element to its parent => keep the same parent + mockMvc.perform(put("/v1/elements?targetDirectoryUuid=" + rootDir10Uuid) + .header("userId", "Doe") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(List.of(elementUUID))) + ) + .andExpect(status().isOk()); // Response status is 200 but nothing changed and no notification should be sent + } + + @Test + public void testDirectoryMoveError() throws Exception { + UUID rootDir1Uuid = insertAndCheckRootDirectory("rootDir1", USER_ID); + + UUID elementUuid1 = UUID.randomUUID(); + ElementAttributes elementAttributes1 = toElementAttributes(elementUuid1, "dir1", DIRECTORY, USER_ID); + insertAndCheckSubElementInRootDir(rootDir1Uuid, elementAttributes1); + + UUID elementUuid2 = UUID.randomUUID(); + ElementAttributes elementAttributes2 = toElementAttributes(elementUuid2, "dir2", DIRECTORY, USER_ID); + insertAndCheckSubElement(elementUuid1, elementAttributes2); + + // test move element to be root directory: targetDirectoryUuid = null + mockMvc.perform(put("/v1/elements") + .header("userId", USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(List.of(elementUuid1))) + ) + .andExpect(status().isBadRequest()); + + // test move element to one of its descendents + mockMvc.perform(put("/v1/elements?targetDirectoryUuid=" + elementUuid2) + .header("userId", USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(List.of(elementUuid1))) + ) + .andExpect(status().isForbidden()); + + // test move element to itself + mockMvc.perform(put("/v1/elements?targetDirectoryUuid=" + elementUuid1) + .header("userId", USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(List.of(elementUuid1))) + ) + .andExpect(status().isForbidden()); + + assertNbElementsInRepositories(3); } @Test @@ -651,7 +737,27 @@ public void testMoveRootDirectory() throws Exception { .header("userId", "Doe") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(List.of(rootDir10Uuid)))) - .andExpect(status().isForbidden()); + .andExpect(status().isOk()); + + Message message = output.receive(TIMEOUT, directoryUpdateDestination); + assertEquals("", new String(message.getPayload())); + MessageHeaders headers = message.getHeaders(); + assertEquals("Doe", headers.get(HEADER_USER_ID)); + assertEquals(rootDir10Uuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); + assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); + assertEquals(NotificationType.DELETE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); + + message = output.receive(TIMEOUT, directoryUpdateDestination); + assertEquals("", new String(message.getPayload())); + headers = message.getHeaders(); + assertEquals("Doe", headers.get(HEADER_USER_ID)); + assertEquals(rootDir20Uuid, headers.get(HEADER_DIRECTORY_UUID)); + assertEquals(true, headers.get(HEADER_IS_ROOT_DIRECTORY)); + assertEquals(true, headers.get(HEADER_IS_PUBLIC_DIRECTORY)); + assertEquals(NotificationType.UPDATE_DIRECTORY, headers.get(HEADER_NOTIFICATION_TYPE)); + assertEquals(UPDATE_TYPE_DIRECTORIES, headers.get(HEADER_UPDATE_TYPE)); assertNbElementsInRepositories(3); } diff --git a/src/test/java/org/gridsuite/directory/server/SupervisionTest.java b/src/test/java/org/gridsuite/directory/server/SupervisionTest.java index cadc1d03..9c3fa37e 100644 --- a/src/test/java/org/gridsuite/directory/server/SupervisionTest.java +++ b/src/test/java/org/gridsuite/directory/server/SupervisionTest.java @@ -78,7 +78,7 @@ void testReindexElements() { List elementPath = List.of(); // No need path for tests verify(directoryElementRepository, times(1)).findAll(); verify(directoryElementInfosRepository, times(1)).saveAll(allElements.stream().map(e -> e.toDirectoryElementInfos(elementPath)).toList()); - verify(directoryElementRepository, times(3)).findElementHierarchy(any(UUID.class)); + verify(directoryElementRepository, times(2)).findElementHierarchy(any(UUID.class)); } @AfterEach