Loading src/main/java/it/inaf/oats/vospace/MoveService.java +24 −11 Original line number Diff line number Diff line Loading @@ -12,10 +12,9 @@ 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.NodeDAO; import java.util.List; import it.inaf.oats.vospace.persistence.NodeDAO.ShortNodeDescriptor; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; Loading Loading @@ -58,6 +57,12 @@ public class MoveService { return; } //Check if destination is subpath of source // Linux-like: "cannot move to a subdirectory of itself" if(destinationPath.startsWith(sourcePath)) { throw new IllegalArgumentException("Cannot move node to a subdirectory of its own path"); } try { // Get source node Optional<Long> sourceIdOpt = nodeDao.getNodeId(sourcePath); Loading @@ -71,14 +76,21 @@ public class MoveService { throw new PermissionDeniedException(sourcePath); } Optional<String> destLtreePath = nodeDao.getLtreePath(destinationPath); Optional<ShortNodeDescriptor> destShortNodeDescriptor = nodeDao.getShortNodeDescriptor(destinationPath, user.getName(), user.getGroups()); String parentDestLtreePath = null; if (destLtreePath.isPresent()) { String destinationNodeLtreePath = null; if (destShortNodeDescriptor.isPresent()) { // When the destination is an existing ContainerNode, the source SHALL be placed under it (i.e., within the container) // TODO: check that this is a ContainerNode using a simple select, otherwise throw an error // maybe we could select the type together with the ltree returned by the previous query parentDestLtreePath = destLtreePath.get(); ShortNodeDescriptor snd = destShortNodeDescriptor.get(); if(snd.isBusy()) throw new NodeBusyException(destinationPath); // TODO: can split permission denied condition further. if(!snd.isWritable()) throw new PermissionDeniedException(destinationPath); if(!snd.isContainer()) throw new InternalFaultException("Existing destination is not a container: " + destinationPath); destinationNodeLtreePath = snd.getDestinationNodeLtreePath(); } else { // Compare source and destination paths parents and see if it's just a rename if (NodeUtils.getParentPath(sourcePath) Loading @@ -91,9 +103,10 @@ public class MoveService { } } if (parentDestLtreePath != null) { nodeDao.moveNodeBranch(sourceId, parentDestLtreePath); if (destinationNodeLtreePath != null) { nodeDao.moveNodeBranch(sourceId, destinationNodeLtreePath); } } catch (CannotSerializeTransactionException ex) { // Concurrent transactions attempted to modify this set of nodes throw new NodeBusyException(sourcePath); Loading src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +73 −45 Original line number Diff line number Diff line Loading @@ -279,6 +279,45 @@ public class NodeDAO { } } public Optional<ShortNodeDescriptor> getShortNodeDescriptor(String nodeVosPath, String userId, List<String> userGroups) { String sql = "SELECT path,\n" + "NOT (n.async_trans OR n.sticky OR loc.location_type = 'async' \n" + "OR ( (SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(n.group_write))) = 0 AND\n" + "n.creator_id <> ?)) AS is_writable,\n" + "n.type = 'container' AS is_container\n" + "n.busy_state\n" + "FROM node n \n" + "JOIN node_vos_path p ON n.node_id = p.node_id \n" + "LEFT JOIN location loc ON loc.location_id = n.location_id\n" + "WHERE vos_path = ?\n"; Optional<ShortNodeDescriptor> sndOpt = jdbcTemplate.query(conn -> { PreparedStatement ps = conn.prepareStatement(sql); ps.setArray(1, ps.getConnection().createArrayOf("varchar", userGroups.toArray(String[]::new))); ps.setString(2, userId); ps.setString(3, nodeVosPath); return ps; }, rs -> { if (!rs.next()) { return Optional.empty(); } String nodePath = rs.getString("path"); Boolean isContainer = rs.getBoolean(("is_container")); Boolean isWritable = rs.getBoolean("is_writable"); Boolean isBusy = rs.getBoolean("busy_state"); ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy); return Optional.of(result); }); return sndOpt; } public Optional<Node> getNodeById(Long nodeId, boolean enforceTapeStoredCheck) { String sql = "SELECT os.vos_path, loc.location_type, n.node_id, type, async_trans, sticky, busy_state, creator_id, group_read, group_write,\n" + "is_public, content_length, created_on, last_modified, accept_views, provide_views\n" Loading Loading @@ -366,54 +405,10 @@ public class NodeDAO { } /* public Optional<Long> getMoveDestinationNodeId(String sourceVosPath, String destVosPath, String userId, List<String> userGroups) { String destParentVosPath = NodeUtils.getParentPath(destVosPath); String sql = "SELECT n.node_id from node n\n" + "JOIN node_vos_path os on n.node_id = os.node_id\n" + "LEFT JOIN location loc on loc.location_id = n.location_id\n" + "WHERE os.vos_path IN( ?, ?)\n" + "AND location_type <> 'async'\n" + "AND (creator_id = ? OR ? && group_write )\n" + "AND NOT n.busy_state AND NOT n.sticky\n" + "ORDER BY path DESC LIMIT 1"; Long result = jdbcTemplate.query(sql, ps -> { ps.setString(1, destVosPath); ps.setString(2, destParentVosPath); ps.setArray(3, ps.getConnection().createArrayOf("varchar", userGroups.toArray(String[]::new))); ps.setString(4, userId); }, row -> { if (!row.next()) { throw new IllegalStateException("Expected one result"); } return row.getLong(1); }); return Optional.ofNullable(result); } */ public void moveNodeBranchNew(String sourceVosPath, Long nodeDestId, String userId, List<String> userGroups) { String sql = "UPDATE node c SET " + "parent_path = (? || SUBPATH(c.path, (SELECT nlevel(parent_path) FROM node WHERE node_id = ?), -1))::ltree, " + "parent_relative_path = COALESCE(c.parent_relative_path, c.parent_path) " // not sure about this + "FROM node n " + "WHERE n.path @> c.path AND n.node_id = ?"; } public void moveNodeBranch(Long sourceRootId, String destParentLtreePath) { String sql = "UPDATE node c SET " + "parent_path = (? || SUBPATH(c.path, (SELECT nlevel(parent_path) FROM node WHERE node_id = ?), -1))::ltree, " + "parent_path = (? || SUBLTREE(c.path, (SELECT nlevel(parent_path) FROM node WHERE node_id = ?), nlevel(c.path)))::ltree, " + "parent_relative_path = COALESCE(c.parent_relative_path, c.parent_path) " // not sure about this + "FROM node n " + "WHERE n.path @> c.path AND n.node_id = ?"; Loading Loading @@ -709,6 +704,39 @@ public class NodeDAO { return paths; } public class ShortNodeDescriptor { private final String nodeLtreePath; private final boolean container; private final boolean writable; private final boolean busy; public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy) { this.nodeLtreePath = nodeLtreePath; this.container = container; this.writable = writable; this.busy = busy; } public String getDestinationNodeLtreePath() { return nodeLtreePath; } public boolean isContainer() { return container; } public boolean isWritable() { return writable; } public boolean isBusy() { return busy; } } private class NodePaths { private final String path; Loading Loading
src/main/java/it/inaf/oats/vospace/MoveService.java +24 −11 Original line number Diff line number Diff line Loading @@ -12,10 +12,9 @@ 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.NodeDAO; import java.util.List; import it.inaf.oats.vospace.persistence.NodeDAO.ShortNodeDescriptor; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; Loading Loading @@ -58,6 +57,12 @@ public class MoveService { return; } //Check if destination is subpath of source // Linux-like: "cannot move to a subdirectory of itself" if(destinationPath.startsWith(sourcePath)) { throw new IllegalArgumentException("Cannot move node to a subdirectory of its own path"); } try { // Get source node Optional<Long> sourceIdOpt = nodeDao.getNodeId(sourcePath); Loading @@ -71,14 +76,21 @@ public class MoveService { throw new PermissionDeniedException(sourcePath); } Optional<String> destLtreePath = nodeDao.getLtreePath(destinationPath); Optional<ShortNodeDescriptor> destShortNodeDescriptor = nodeDao.getShortNodeDescriptor(destinationPath, user.getName(), user.getGroups()); String parentDestLtreePath = null; if (destLtreePath.isPresent()) { String destinationNodeLtreePath = null; if (destShortNodeDescriptor.isPresent()) { // When the destination is an existing ContainerNode, the source SHALL be placed under it (i.e., within the container) // TODO: check that this is a ContainerNode using a simple select, otherwise throw an error // maybe we could select the type together with the ltree returned by the previous query parentDestLtreePath = destLtreePath.get(); ShortNodeDescriptor snd = destShortNodeDescriptor.get(); if(snd.isBusy()) throw new NodeBusyException(destinationPath); // TODO: can split permission denied condition further. if(!snd.isWritable()) throw new PermissionDeniedException(destinationPath); if(!snd.isContainer()) throw new InternalFaultException("Existing destination is not a container: " + destinationPath); destinationNodeLtreePath = snd.getDestinationNodeLtreePath(); } else { // Compare source and destination paths parents and see if it's just a rename if (NodeUtils.getParentPath(sourcePath) Loading @@ -91,9 +103,10 @@ public class MoveService { } } if (parentDestLtreePath != null) { nodeDao.moveNodeBranch(sourceId, parentDestLtreePath); if (destinationNodeLtreePath != null) { nodeDao.moveNodeBranch(sourceId, destinationNodeLtreePath); } } catch (CannotSerializeTransactionException ex) { // Concurrent transactions attempted to modify this set of nodes throw new NodeBusyException(sourcePath); Loading
src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +73 −45 Original line number Diff line number Diff line Loading @@ -279,6 +279,45 @@ public class NodeDAO { } } public Optional<ShortNodeDescriptor> getShortNodeDescriptor(String nodeVosPath, String userId, List<String> userGroups) { String sql = "SELECT path,\n" + "NOT (n.async_trans OR n.sticky OR loc.location_type = 'async' \n" + "OR ( (SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(n.group_write))) = 0 AND\n" + "n.creator_id <> ?)) AS is_writable,\n" + "n.type = 'container' AS is_container\n" + "n.busy_state\n" + "FROM node n \n" + "JOIN node_vos_path p ON n.node_id = p.node_id \n" + "LEFT JOIN location loc ON loc.location_id = n.location_id\n" + "WHERE vos_path = ?\n"; Optional<ShortNodeDescriptor> sndOpt = jdbcTemplate.query(conn -> { PreparedStatement ps = conn.prepareStatement(sql); ps.setArray(1, ps.getConnection().createArrayOf("varchar", userGroups.toArray(String[]::new))); ps.setString(2, userId); ps.setString(3, nodeVosPath); return ps; }, rs -> { if (!rs.next()) { return Optional.empty(); } String nodePath = rs.getString("path"); Boolean isContainer = rs.getBoolean(("is_container")); Boolean isWritable = rs.getBoolean("is_writable"); Boolean isBusy = rs.getBoolean("busy_state"); ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy); return Optional.of(result); }); return sndOpt; } public Optional<Node> getNodeById(Long nodeId, boolean enforceTapeStoredCheck) { String sql = "SELECT os.vos_path, loc.location_type, n.node_id, type, async_trans, sticky, busy_state, creator_id, group_read, group_write,\n" + "is_public, content_length, created_on, last_modified, accept_views, provide_views\n" Loading Loading @@ -366,54 +405,10 @@ public class NodeDAO { } /* public Optional<Long> getMoveDestinationNodeId(String sourceVosPath, String destVosPath, String userId, List<String> userGroups) { String destParentVosPath = NodeUtils.getParentPath(destVosPath); String sql = "SELECT n.node_id from node n\n" + "JOIN node_vos_path os on n.node_id = os.node_id\n" + "LEFT JOIN location loc on loc.location_id = n.location_id\n" + "WHERE os.vos_path IN( ?, ?)\n" + "AND location_type <> 'async'\n" + "AND (creator_id = ? OR ? && group_write )\n" + "AND NOT n.busy_state AND NOT n.sticky\n" + "ORDER BY path DESC LIMIT 1"; Long result = jdbcTemplate.query(sql, ps -> { ps.setString(1, destVosPath); ps.setString(2, destParentVosPath); ps.setArray(3, ps.getConnection().createArrayOf("varchar", userGroups.toArray(String[]::new))); ps.setString(4, userId); }, row -> { if (!row.next()) { throw new IllegalStateException("Expected one result"); } return row.getLong(1); }); return Optional.ofNullable(result); } */ public void moveNodeBranchNew(String sourceVosPath, Long nodeDestId, String userId, List<String> userGroups) { String sql = "UPDATE node c SET " + "parent_path = (? || SUBPATH(c.path, (SELECT nlevel(parent_path) FROM node WHERE node_id = ?), -1))::ltree, " + "parent_relative_path = COALESCE(c.parent_relative_path, c.parent_path) " // not sure about this + "FROM node n " + "WHERE n.path @> c.path AND n.node_id = ?"; } public void moveNodeBranch(Long sourceRootId, String destParentLtreePath) { String sql = "UPDATE node c SET " + "parent_path = (? || SUBPATH(c.path, (SELECT nlevel(parent_path) FROM node WHERE node_id = ?), -1))::ltree, " + "parent_path = (? || SUBLTREE(c.path, (SELECT nlevel(parent_path) FROM node WHERE node_id = ?), nlevel(c.path)))::ltree, " + "parent_relative_path = COALESCE(c.parent_relative_path, c.parent_path) " // not sure about this + "FROM node n " + "WHERE n.path @> c.path AND n.node_id = ?"; Loading Loading @@ -709,6 +704,39 @@ public class NodeDAO { return paths; } public class ShortNodeDescriptor { private final String nodeLtreePath; private final boolean container; private final boolean writable; private final boolean busy; public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy) { this.nodeLtreePath = nodeLtreePath; this.container = container; this.writable = writable; this.busy = busy; } public String getDestinationNodeLtreePath() { return nodeLtreePath; } public boolean isContainer() { return container; } public boolean isWritable() { return writable; } public boolean isBusy() { return busy; } } private class NodePaths { private final String path; Loading