Loading src/main/java/it/inaf/oats/vospace/CopyService.java +2 −16 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ public class CopyService { // check source branch for read and lock it nodeBranchService.checkBranchForReadAndLock(sourcePath, jobId); copyNode(sourcePath, destinationPath, jobId); nodeDao.copyBranch(sourcePath, destinationPath, jobId); } Loading @@ -76,18 +76,4 @@ public class CopyService { } } private void copyNode(String vosSourceNode, String vosDestNode, String jobId) { String sourceName = NodeUtils.getNodeName(vosSourceNode); String destPath = vosDestNode + "/" + sourceName; nodeDao.copySingleNode(sourceName, destPath, jobId); List<String> children = nodeDao.listNodeChildren(sourceName); if (!children.isEmpty()) { for (String n : children) { this.copyNode(sourceName + "/" +n, destPath+"/"+n, jobId); } } } } src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +52 −45 Original line number Diff line number Diff line Loading @@ -339,46 +339,53 @@ public class NodeDAO { }); } public void copySingleNode(String sourceVosPath, String destVosPath, String jobId) { // Select source node String selectSourceSQL = "SELECT\n" + "c.path, c.node_id, c.parent_path, c.parent_relative_path, c.name, c.os_name, c.tstamp_wrapper_dir,\n" + "c.type, c.location_id, c.format, c.async_trans, c.sticky, c.creator_id,\n" + "c.group_read, c.group_write, c.is_public, c.delta, c.content_type, c.content_encoding,\n" + "c.content_length, c.content_md5, c.accept_views, c.provide_views, c.protocols\n" + "FROM node c\n" + "WHERE c.node_id = id_from_vos_path(?)"; // Select destination node necessary information String selectDestinationSQL = "SELECT\n" + "d.path, d.parent_path, d.parent_relative_path\n" + "FROM node d\n" + "WHERE d.node_id = id_from_vos_path(?)"; // Insert branch String insertSQL = "INSERT INTO node\n" + "(parent_path, parent_relative_path, job_id, name, os_name, tstamp_wrapper_dir,\n" + "type, location_id, format, async_trans, sticky, creator_id,\n" + "group_read, group_write, is_public, delta, content_type, content_encoding,\n" + "content_length, content_md5, accept_views, provide_views, protocols)\n" + "VALUES\n" + "(cte_dest.path, COALESCE(cte_dest.parent_relative_path, cte_dest.path), ?,\n" + "cte_source.name, cte_source.os_name, cte_source.tstamp_wrapper_dir,\n" + "cte_source.type, cte_source.location_id, cte_source.format, cte_source.async_trans, cte_source.sticky, cte_source.creator_id,\n" + "cte_source.group_read, cte_source.group_write, cte_source.is_public, cte_source.delta, cte_source.content_type, cte_source.content_encoding,\n" + "cte_source.content_length, cte_source.content_md5, cte_source.accept_views, cte_source.provide_views, cte_source.protocols)\n"; String cteSQL = "WITH cte_source AS (" + selectSourceSQL + "),\n" + "cte_dest AS (" + selectDestinationSQL + ")\n" + insertSQL; public void copyBranch(String sourceVosPath, String destVosPath, String jobId) { String destVosParentPath = NodeUtils.getParentPath(destVosPath); String destName = NodeUtils.getNodeName(destVosPath); String parentInsert = "INSERT INTO node (node_id, parent_path, parent_relative_path, name, type, location_id, creator_id, group_write, group_read, is_public, job_id)\n"; String ctePathPrefix = "SELECT CASE WHEN path::varchar = '' THEN '' ELSE (path::varchar || '.') END AS prefix\n" + "FROM node WHERE node_id = id_from_vos_path(?)"; String cteCopiedNodes = "SELECT nextval('node_node_id_seq') AS new_node_id,\n" + "((SELECT prefix FROM path_prefix) || currval('node_node_id_seq'))::ltree AS new_path,\n" + "path, relative_path, parent_path, parent_relative_path, ? AS name,\n" + "type, location_id, creator_id, group_write, group_read, is_public\n" + "FROM node WHERE node_id = id_from_vos_path(?)\n" + "UNION ALL\n" + "SELECT nextval('node_node_id_seq') AS new_node_id,\n" + "(p.new_path::varchar || '.' || currval('node_node_id_seq'))::ltree,\n" + "n.path, n.relative_path, n.parent_path, n.parent_relative_path, n.name,\n" + "n.type, n.location_id, n.creator_id, n.group_write, n.group_read, n.is_public\n" + "FROM node n\n" + "JOIN copied_nodes p ON p.path = n.parent_path"; String cteCopiedNodesPaths = "SELECT subpath(new_path, 0, nlevel(new_path) - 1) AS new_parent_path,\n" + "nlevel(parent_path) - nlevel(parent_relative_path) AS rel_offset, * FROM copied_nodes"; String parentSelect = "SELECT\n" + "new_node_id, new_parent_path,\n" + "CASE WHEN nlevel(new_parent_path) = rel_offset THEN ''::ltree ELSE subpath(new_parent_path, rel_offset) END new_parent_relative_path,\n" + "name, type, location_id, creator_id, group_write, group_read, is_public, ?\n" + "FROM copied_nodes_paths"; String sql = parentInsert + "WITH RECURSIVE path_prefix AS (" + ctePathPrefix + "),\n" + "copied_nodes AS (" + cteCopiedNodes + "),\n" + "copied_nodes_paths AS (" + cteCopiedNodesPaths + ")\n" + parentSelect; jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement(cteSQL); ps.setString(1, sourceVosPath); ps.setString(2, destVosPath); ps.setString(3, jobId); PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, destVosParentPath); ps.setString(2, destName); ps.setString(3, sourceVosPath); ps.setString(4, jobId); return ps; }); Loading src/test/java/it/inaf/oats/vospace/CopyServiceTest.java 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.exception.NodeBusyException; import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.persistence.DataSourceConfigSingleton; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.Arrays; import java.util.List; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Transfer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.ContextConfiguration; @SpringBootTest @AutoConfigureMockMvc @ContextConfiguration(classes = {DataSourceConfigSingleton.class, CopyServiceTest.TestConfig.class}) @TestPropertySource(locations = "classpath:test.properties", properties = {"vospace-authority=example.com!vospace", "file-service-url=http://file-service"}) @TestMethodOrder(OrderAnnotation.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CopyServiceTest { @Value("${vospace-authority}") private String authority; @Autowired private MoveService moveService; @Autowired private NodeDAO nodeDao; @Autowired private HttpServletRequest servletRequest; @TestConfiguration public static class TestConfig { /** * Necessary because MockBean doesn't work with HttpServletRequest. */ @Bean @Primary public HttpServletRequest servletRequest() { HttpServletRequest request = mock(HttpServletRequest.class); User user = new User().setUserId("anonymous"); when(request.getUserPrincipal()).thenReturn(user); return request; } } // Stub Test Class } src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java +32 −4 Original line number Diff line number Diff line Loading @@ -226,7 +226,7 @@ public class NodeDAOTest { dao.moveNodeBranch(optSourceId.get(), snd.getDestinationNodeLtreePath()); Optional<Long> optResultId = dao.getNodeId("/test3/group1/m1"); assertTrue(optSourceId.isPresent()); assertTrue(optResultId.isPresent()); optSnd = dao.getShortNodeDescriptor("/test3/group1/m1", "user3", List.of("group1")); assertEquals("9.17.10", optSnd.get().getDestinationNodeLtreePath()); Loading @@ -237,6 +237,34 @@ public class NodeDAOTest { } @Test public void testCopyNodeBranch() { // Let's copy /test3/m1 to /test3/group1 Optional<Long> optSourceId = dao.getNodeId("/test3/m1"); assertTrue(optSourceId.isPresent()); Optional<Long> optSourceChildId = dao.getNodeId("/test3/m1/m2"); assertTrue(optSourceChildId.isPresent()); Optional<Long> optDestParentId = dao.getNodeId("/test3/group1"); assertTrue(optDestParentId.isPresent()); dao.copyBranch("/test3/m1", "/test3/group1/copy_of_m1", "pippo"); Optional<Long> resultId = dao.getNodeId("/test3/group1/copy_of_m1"); assertTrue(resultId.isPresent()); Optional<Long> recheckSource = dao.getNodeId("/test3/m1"); assertTrue(recheckSource.isPresent()); Optional<Long> resultIdChild = dao.getNodeId("/test3/group1/copy_of_m1/m2"); assertTrue(resultIdChild.isPresent()); Optional<Long> recheckSourceChild = dao.getNodeId("/test3/m1/m2"); assertTrue(recheckSourceChild.isPresent()); } @Test public void testRenameNode() { String oldPath = "/test1/f1"; Loading Loading
src/main/java/it/inaf/oats/vospace/CopyService.java +2 −16 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ public class CopyService { // check source branch for read and lock it nodeBranchService.checkBranchForReadAndLock(sourcePath, jobId); copyNode(sourcePath, destinationPath, jobId); nodeDao.copyBranch(sourcePath, destinationPath, jobId); } Loading @@ -76,18 +76,4 @@ public class CopyService { } } private void copyNode(String vosSourceNode, String vosDestNode, String jobId) { String sourceName = NodeUtils.getNodeName(vosSourceNode); String destPath = vosDestNode + "/" + sourceName; nodeDao.copySingleNode(sourceName, destPath, jobId); List<String> children = nodeDao.listNodeChildren(sourceName); if (!children.isEmpty()) { for (String n : children) { this.copyNode(sourceName + "/" +n, destPath+"/"+n, jobId); } } } }
src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +52 −45 Original line number Diff line number Diff line Loading @@ -339,46 +339,53 @@ public class NodeDAO { }); } public void copySingleNode(String sourceVosPath, String destVosPath, String jobId) { // Select source node String selectSourceSQL = "SELECT\n" + "c.path, c.node_id, c.parent_path, c.parent_relative_path, c.name, c.os_name, c.tstamp_wrapper_dir,\n" + "c.type, c.location_id, c.format, c.async_trans, c.sticky, c.creator_id,\n" + "c.group_read, c.group_write, c.is_public, c.delta, c.content_type, c.content_encoding,\n" + "c.content_length, c.content_md5, c.accept_views, c.provide_views, c.protocols\n" + "FROM node c\n" + "WHERE c.node_id = id_from_vos_path(?)"; // Select destination node necessary information String selectDestinationSQL = "SELECT\n" + "d.path, d.parent_path, d.parent_relative_path\n" + "FROM node d\n" + "WHERE d.node_id = id_from_vos_path(?)"; // Insert branch String insertSQL = "INSERT INTO node\n" + "(parent_path, parent_relative_path, job_id, name, os_name, tstamp_wrapper_dir,\n" + "type, location_id, format, async_trans, sticky, creator_id,\n" + "group_read, group_write, is_public, delta, content_type, content_encoding,\n" + "content_length, content_md5, accept_views, provide_views, protocols)\n" + "VALUES\n" + "(cte_dest.path, COALESCE(cte_dest.parent_relative_path, cte_dest.path), ?,\n" + "cte_source.name, cte_source.os_name, cte_source.tstamp_wrapper_dir,\n" + "cte_source.type, cte_source.location_id, cte_source.format, cte_source.async_trans, cte_source.sticky, cte_source.creator_id,\n" + "cte_source.group_read, cte_source.group_write, cte_source.is_public, cte_source.delta, cte_source.content_type, cte_source.content_encoding,\n" + "cte_source.content_length, cte_source.content_md5, cte_source.accept_views, cte_source.provide_views, cte_source.protocols)\n"; String cteSQL = "WITH cte_source AS (" + selectSourceSQL + "),\n" + "cte_dest AS (" + selectDestinationSQL + ")\n" + insertSQL; public void copyBranch(String sourceVosPath, String destVosPath, String jobId) { String destVosParentPath = NodeUtils.getParentPath(destVosPath); String destName = NodeUtils.getNodeName(destVosPath); String parentInsert = "INSERT INTO node (node_id, parent_path, parent_relative_path, name, type, location_id, creator_id, group_write, group_read, is_public, job_id)\n"; String ctePathPrefix = "SELECT CASE WHEN path::varchar = '' THEN '' ELSE (path::varchar || '.') END AS prefix\n" + "FROM node WHERE node_id = id_from_vos_path(?)"; String cteCopiedNodes = "SELECT nextval('node_node_id_seq') AS new_node_id,\n" + "((SELECT prefix FROM path_prefix) || currval('node_node_id_seq'))::ltree AS new_path,\n" + "path, relative_path, parent_path, parent_relative_path, ? AS name,\n" + "type, location_id, creator_id, group_write, group_read, is_public\n" + "FROM node WHERE node_id = id_from_vos_path(?)\n" + "UNION ALL\n" + "SELECT nextval('node_node_id_seq') AS new_node_id,\n" + "(p.new_path::varchar || '.' || currval('node_node_id_seq'))::ltree,\n" + "n.path, n.relative_path, n.parent_path, n.parent_relative_path, n.name,\n" + "n.type, n.location_id, n.creator_id, n.group_write, n.group_read, n.is_public\n" + "FROM node n\n" + "JOIN copied_nodes p ON p.path = n.parent_path"; String cteCopiedNodesPaths = "SELECT subpath(new_path, 0, nlevel(new_path) - 1) AS new_parent_path,\n" + "nlevel(parent_path) - nlevel(parent_relative_path) AS rel_offset, * FROM copied_nodes"; String parentSelect = "SELECT\n" + "new_node_id, new_parent_path,\n" + "CASE WHEN nlevel(new_parent_path) = rel_offset THEN ''::ltree ELSE subpath(new_parent_path, rel_offset) END new_parent_relative_path,\n" + "name, type, location_id, creator_id, group_write, group_read, is_public, ?\n" + "FROM copied_nodes_paths"; String sql = parentInsert + "WITH RECURSIVE path_prefix AS (" + ctePathPrefix + "),\n" + "copied_nodes AS (" + cteCopiedNodes + "),\n" + "copied_nodes_paths AS (" + cteCopiedNodesPaths + ")\n" + parentSelect; jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement(cteSQL); ps.setString(1, sourceVosPath); ps.setString(2, destVosPath); ps.setString(3, jobId); PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, destVosParentPath); ps.setString(2, destName); ps.setString(3, sourceVosPath); ps.setString(4, jobId); return ps; }); Loading
src/test/java/it/inaf/oats/vospace/CopyServiceTest.java 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.exception.NodeBusyException; import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.persistence.DataSourceConfigSingleton; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.Arrays; import java.util.List; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Transfer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.ContextConfiguration; @SpringBootTest @AutoConfigureMockMvc @ContextConfiguration(classes = {DataSourceConfigSingleton.class, CopyServiceTest.TestConfig.class}) @TestPropertySource(locations = "classpath:test.properties", properties = {"vospace-authority=example.com!vospace", "file-service-url=http://file-service"}) @TestMethodOrder(OrderAnnotation.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class CopyServiceTest { @Value("${vospace-authority}") private String authority; @Autowired private MoveService moveService; @Autowired private NodeDAO nodeDao; @Autowired private HttpServletRequest servletRequest; @TestConfiguration public static class TestConfig { /** * Necessary because MockBean doesn't work with HttpServletRequest. */ @Bean @Primary public HttpServletRequest servletRequest() { HttpServletRequest request = mock(HttpServletRequest.class); User user = new User().setUserId("anonymous"); when(request.getUserPrincipal()).thenReturn(user); return request; } } // Stub Test Class }
src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java +32 −4 Original line number Diff line number Diff line Loading @@ -226,7 +226,7 @@ public class NodeDAOTest { dao.moveNodeBranch(optSourceId.get(), snd.getDestinationNodeLtreePath()); Optional<Long> optResultId = dao.getNodeId("/test3/group1/m1"); assertTrue(optSourceId.isPresent()); assertTrue(optResultId.isPresent()); optSnd = dao.getShortNodeDescriptor("/test3/group1/m1", "user3", List.of("group1")); assertEquals("9.17.10", optSnd.get().getDestinationNodeLtreePath()); Loading @@ -237,6 +237,34 @@ public class NodeDAOTest { } @Test public void testCopyNodeBranch() { // Let's copy /test3/m1 to /test3/group1 Optional<Long> optSourceId = dao.getNodeId("/test3/m1"); assertTrue(optSourceId.isPresent()); Optional<Long> optSourceChildId = dao.getNodeId("/test3/m1/m2"); assertTrue(optSourceChildId.isPresent()); Optional<Long> optDestParentId = dao.getNodeId("/test3/group1"); assertTrue(optDestParentId.isPresent()); dao.copyBranch("/test3/m1", "/test3/group1/copy_of_m1", "pippo"); Optional<Long> resultId = dao.getNodeId("/test3/group1/copy_of_m1"); assertTrue(resultId.isPresent()); Optional<Long> recheckSource = dao.getNodeId("/test3/m1"); assertTrue(recheckSource.isPresent()); Optional<Long> resultIdChild = dao.getNodeId("/test3/group1/copy_of_m1/m2"); assertTrue(resultIdChild.isPresent()); Optional<Long> recheckSourceChild = dao.getNodeId("/test3/m1/m2"); assertTrue(recheckSourceChild.isPresent()); } @Test public void testRenameNode() { String oldPath = "/test1/f1"; Loading