diff --git a/src/main/java/it/inaf/oats/vospace/MoveService.java b/src/main/java/it/inaf/oats/vospace/MoveService.java index cabf4791bb585b4ea13f3f3003fc8bac7c9ab054..45b3b8f27d3b76c7c668dd051eb21f73e7dda986 100644 --- a/src/main/java/it/inaf/oats/vospace/MoveService.java +++ b/src/main/java/it/inaf/oats/vospace/MoveService.java @@ -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; @@ -57,6 +56,12 @@ public class MoveService { if (sourcePath.equals(destinationPath)) { 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 @@ -69,16 +74,23 @@ public class MoveService { if (!nodeDao.isBranchWritable(sourceId, user.getName(), user.getGroups())) { throw new PermissionDeniedException(sourcePath); - } + } - Optional destLtreePath = nodeDao.getLtreePath(destinationPath); + Optional 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) @@ -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); diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java index a85e6e2bf76ed8a20bdffa672c78b72b0a26d28d..af5ecdbab62b8965f11651318af7345035c0a689 100644 --- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java @@ -279,6 +279,45 @@ public class NodeDAO { } } + public Optional getShortNodeDescriptor(String nodeVosPath, + String userId, List 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 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 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" @@ -366,54 +405,10 @@ public class NodeDAO { } - /* - public Optional getMoveDestinationNodeId(String sourceVosPath, String destVosPath, String userId, List 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 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 = ?"; @@ -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;