Loading src/main/java/it/inaf/oats/vospace/DeleteNodeController.java +11 −63 Original line number Diff line number Diff line Loading @@ -6,14 +6,7 @@ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.LinkFoundException; 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 javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; Loading @@ -23,14 +16,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DeleteNodeController extends BaseNodeController { private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class); @Autowired private NodeDAO nodeDAO; DeleteNodeService deleteNodeService; private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class); @DeleteMapping(value = {"/nodes", "/nodes/**"}, produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) Loading @@ -39,54 +31,10 @@ public class DeleteNodeController extends BaseNodeController { String path = getPath(); LOG.debug("deleteNode called for path {}", path); // Check if the node is present, // if the node does not exist the service SHALL throw a HTTP 404 status code // including a NodeNotFound fault in the entity-body // If note present, got it Node toBeDeletedNode = nodeDAO.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); // If a parent node in the URI path is a LinkNode, the service SHALL throw // a HTTP 400 status code including a LinkFound fault in the entity-body. // For example, given the URI path /a/b/c, the service must throw a HTTP 400 // status code including a LinkFound fault in the entity-body if either /a // or /a/b are LinkNodes. List<String> pathComponents = NodeUtils.subPathComponents(path); if (pathComponents.isEmpty()) { // Manage root node throw PermissionDeniedException.forPath("/"); } else { // Manage all precursors in full path for (int i = 0; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDAO.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size()-1) // a LinkNode leaf can be deleted throw new LinkFoundException(tmpPath); } } if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) { throw PermissionDeniedException.forPath(path); } try { nodeDAO.deleteNode(path); deleteNodeService.doPreliminaryChecks(path); deleteNodeService.deleteNode(path, principal); return ResponseEntity.ok("Node deleted"); } catch(Exception ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST); } } } src/main/java/it/inaf/oats/vospace/DeleteNodeService.java 0 → 100644 +99 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.LinkFoundException; 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 net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Isolation; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.exception.NodeBusyException; import org.springframework.transaction.annotation.Transactional; /** * * @author Nicola Fulvio Calabria <nicola.calabria at inaf.it> */ @Service @EnableTransactionManagement public class DeleteNodeService { @Autowired protected NodeDAO nodeDao; @Value("${vospace-authority}") protected String authority; @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ) public void deleteNode(String path, User principal) { Node toBeDeletedNode = nodeDao.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) { throw PermissionDeniedException.forPath(path); } Long nodeId = nodeDao.getNodeId(path).get(); if (nodeDao.isBranchBusy(nodeId)) { throw new NodeBusyException(path); } if (nodeDao.isBranchImmutable(nodeId)) { throw new InternalFaultException("Target branch contains immutable nodes"); } nodeDao.deleteNode(path); } public void doPreliminaryChecks(String path) { // Check if the node is present, // if the node does not exist the service SHALL throw a HTTP 404 status code // including a NodeNotFound fault in the entity-body // If note present, got it nodeDao.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); // If a parent node in the URI path is a LinkNode, the service SHALL throw // a HTTP 400 status code including a LinkFound fault in the entity-body. // For example, given the URI path /a/b/c, the service must throw a HTTP 400 // status code including a LinkFound fault in the entity-body if either /a // or /a/b are LinkNodes. if (path.equals("/")) { // Manage root node throw PermissionDeniedException.forPath("/"); } else { List<String> pathComponents = NodeUtils.subPathComponents(path); // Manage all precursors in full path for (int i = 0; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDao.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size() - 1) // a LinkNode leaf can be deleted { throw new LinkFoundException(tmpPath); } } } } } src/main/java/it/inaf/oats/vospace/ImmutableService.java 0 → 100644 +40 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.persistence.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; /** * * @author Nicola Fulvio Calabria <nicola.calabria at inaf.it> */ @Service @EnableTransactionManagement public class ImmutableService { @Autowired protected NodeDAO nodeDao; @Value("${vospace-authority}") protected String authority; @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ) public void setBranchImmutable(String rootNodeURI, boolean setImmutable, User user) { String rootNodeVosPath = URIUtils.returnVosPathFromNodeURI(rootNodeURI, authority); // Check if branch is busy } } src/main/java/it/inaf/oats/vospace/MoveService.java +6 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,11 @@ public class MoveService extends AbstractNodeService { throw new NodeBusyException(sourcePath); } // TODO create immutable node exception flavor if (nodeDao.isBranchImmutable(sourceId)) { throw new InternalFaultException("Source branch contains immutable nodes"); } if (!nodeDao.isBranchWritable(sourceId, user.getName(), user.getGroups())) { throw PermissionDeniedException.forPath(sourcePath); } Loading @@ -84,6 +89,7 @@ public class MoveService extends AbstractNodeService { if(snd.isPermissionDenied()) throw PermissionDeniedException.forPath(destinationPath); if(!snd.isWritable()) throw new InternalFaultException("Destination is not writable: "+ destinationPath); if(!snd.isContainer()) throw new InternalFaultException("Existing destination is not a container: " + destinationPath); if(snd.isImmutable()) throw new InternalFaultException("Destination is immutable: " + destinationPath); destinationNodeLtreePath = snd.getDestinationNodeLtreePath(); Loading src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +35 −3 Original line number Diff line number Diff line Loading @@ -256,7 +256,8 @@ public class NodeDAO { + "((SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(n.group_write)) AS allowed_groups ) = 0 AND\n" + "n.creator_id <> ?) AS is_permission_denied,\n" + "n.type = 'container' AS is_container,\n" + "n.job_id IS NOT NULL AS busy_state\n" + "n.job_id IS NOT NULL AS busy_state,\n" + "n.immutable AS is_immutable\n" + "FROM node n \n" + "LEFT JOIN location loc ON loc.location_id = n.location_id\n" + "WHERE n.node_id = id_from_vos_path(?)\n"; Loading Loading @@ -286,8 +287,9 @@ public class NodeDAO { Boolean isBusy = rs.getBoolean("busy_state"); Boolean isPermissionDenied = rs.getBoolean("is_permission_denied"); Boolean isSticky = rs.getBoolean("is_sticky"); Boolean isImmutable = rs.getBoolean("is_immutable"); ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy, isPermissionDenied, isSticky); ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy, isPermissionDenied, isSticky, isImmutable); return Optional.of(result); }); Loading Loading @@ -404,6 +406,30 @@ public class NodeDAO { }); } public void setBranchImmutable(Long rootNodeId, boolean setImmutable) { String sql = "UPDATE node c SET immutable = ? " + "FROM node r " + "WHERE r.node_id = ? " + "AND r.path @> c.path"; jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement(sql); ps.setBoolean(1, setImmutable); ps.setLong(2, rootNodeId); return ps; }); } public boolean isBranchImmutable(long parentNodeId) { String sql = "SELECT COUNT(c.node_id) > 0 " + "FROM node n " + "JOIN node c ON c.path <@ n.path " + "WHERE n.node_id = ? AND c.immutable IS TRUE"; return jdbcTemplate.queryForObject(sql, new Object[]{parentNodeId}, new int[]{Types.BIGINT}, Boolean.class); } public void releaseBusyNodesByJobId(String jobId) { String sql = "UPDATE node SET job_id = NULL WHERE job_id = ?"; Loading Loading @@ -742,14 +768,16 @@ public class NodeDAO { private final boolean busy; private final boolean permissionDenied; private final boolean sticky; private final boolean immutable; public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy, boolean permissionDenied, boolean sticky) { public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy, boolean permissionDenied, boolean sticky, boolean immutable) { this.nodeLtreePath = nodeLtreePath; this.container = container; this.writable = writable; this.busy = busy; this.permissionDenied = permissionDenied; this.sticky = sticky; this.immutable = immutable; } public String getDestinationNodeLtreePath() { Loading @@ -760,6 +788,10 @@ public class NodeDAO { return container; } public boolean isImmutable() { return immutable; } public boolean isWritable() { return writable; } Loading Loading
src/main/java/it/inaf/oats/vospace/DeleteNodeController.java +11 −63 Original line number Diff line number Diff line Loading @@ -6,14 +6,7 @@ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.LinkFoundException; 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 javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; Loading @@ -23,14 +16,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DeleteNodeController extends BaseNodeController { private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class); @Autowired private NodeDAO nodeDAO; DeleteNodeService deleteNodeService; private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class); @DeleteMapping(value = {"/nodes", "/nodes/**"}, produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) Loading @@ -39,54 +31,10 @@ public class DeleteNodeController extends BaseNodeController { String path = getPath(); LOG.debug("deleteNode called for path {}", path); // Check if the node is present, // if the node does not exist the service SHALL throw a HTTP 404 status code // including a NodeNotFound fault in the entity-body // If note present, got it Node toBeDeletedNode = nodeDAO.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); // If a parent node in the URI path is a LinkNode, the service SHALL throw // a HTTP 400 status code including a LinkFound fault in the entity-body. // For example, given the URI path /a/b/c, the service must throw a HTTP 400 // status code including a LinkFound fault in the entity-body if either /a // or /a/b are LinkNodes. List<String> pathComponents = NodeUtils.subPathComponents(path); if (pathComponents.isEmpty()) { // Manage root node throw PermissionDeniedException.forPath("/"); } else { // Manage all precursors in full path for (int i = 0; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDAO.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size()-1) // a LinkNode leaf can be deleted throw new LinkFoundException(tmpPath); } } if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) { throw PermissionDeniedException.forPath(path); } try { nodeDAO.deleteNode(path); deleteNodeService.doPreliminaryChecks(path); deleteNodeService.deleteNode(path, principal); return ResponseEntity.ok("Node deleted"); } catch(Exception ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST); } } }
src/main/java/it/inaf/oats/vospace/DeleteNodeService.java 0 → 100644 +99 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.LinkFoundException; 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 net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Isolation; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.exception.NodeBusyException; import org.springframework.transaction.annotation.Transactional; /** * * @author Nicola Fulvio Calabria <nicola.calabria at inaf.it> */ @Service @EnableTransactionManagement public class DeleteNodeService { @Autowired protected NodeDAO nodeDao; @Value("${vospace-authority}") protected String authority; @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ) public void deleteNode(String path, User principal) { Node toBeDeletedNode = nodeDao.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) { throw PermissionDeniedException.forPath(path); } Long nodeId = nodeDao.getNodeId(path).get(); if (nodeDao.isBranchBusy(nodeId)) { throw new NodeBusyException(path); } if (nodeDao.isBranchImmutable(nodeId)) { throw new InternalFaultException("Target branch contains immutable nodes"); } nodeDao.deleteNode(path); } public void doPreliminaryChecks(String path) { // Check if the node is present, // if the node does not exist the service SHALL throw a HTTP 404 status code // including a NodeNotFound fault in the entity-body // If note present, got it nodeDao.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); // If a parent node in the URI path is a LinkNode, the service SHALL throw // a HTTP 400 status code including a LinkFound fault in the entity-body. // For example, given the URI path /a/b/c, the service must throw a HTTP 400 // status code including a LinkFound fault in the entity-body if either /a // or /a/b are LinkNodes. if (path.equals("/")) { // Manage root node throw PermissionDeniedException.forPath("/"); } else { List<String> pathComponents = NodeUtils.subPathComponents(path); // Manage all precursors in full path for (int i = 0; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDao.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size() - 1) // a LinkNode leaf can be deleted { throw new LinkFoundException(tmpPath); } } } } }
src/main/java/it/inaf/oats/vospace/ImmutableService.java 0 → 100644 +40 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.persistence.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; /** * * @author Nicola Fulvio Calabria <nicola.calabria at inaf.it> */ @Service @EnableTransactionManagement public class ImmutableService { @Autowired protected NodeDAO nodeDao; @Value("${vospace-authority}") protected String authority; @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ) public void setBranchImmutable(String rootNodeURI, boolean setImmutable, User user) { String rootNodeVosPath = URIUtils.returnVosPathFromNodeURI(rootNodeURI, authority); // Check if branch is busy } }
src/main/java/it/inaf/oats/vospace/MoveService.java +6 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,11 @@ public class MoveService extends AbstractNodeService { throw new NodeBusyException(sourcePath); } // TODO create immutable node exception flavor if (nodeDao.isBranchImmutable(sourceId)) { throw new InternalFaultException("Source branch contains immutable nodes"); } if (!nodeDao.isBranchWritable(sourceId, user.getName(), user.getGroups())) { throw PermissionDeniedException.forPath(sourcePath); } Loading @@ -84,6 +89,7 @@ public class MoveService extends AbstractNodeService { if(snd.isPermissionDenied()) throw PermissionDeniedException.forPath(destinationPath); if(!snd.isWritable()) throw new InternalFaultException("Destination is not writable: "+ destinationPath); if(!snd.isContainer()) throw new InternalFaultException("Existing destination is not a container: " + destinationPath); if(snd.isImmutable()) throw new InternalFaultException("Destination is immutable: " + destinationPath); destinationNodeLtreePath = snd.getDestinationNodeLtreePath(); Loading
src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +35 −3 Original line number Diff line number Diff line Loading @@ -256,7 +256,8 @@ public class NodeDAO { + "((SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(n.group_write)) AS allowed_groups ) = 0 AND\n" + "n.creator_id <> ?) AS is_permission_denied,\n" + "n.type = 'container' AS is_container,\n" + "n.job_id IS NOT NULL AS busy_state\n" + "n.job_id IS NOT NULL AS busy_state,\n" + "n.immutable AS is_immutable\n" + "FROM node n \n" + "LEFT JOIN location loc ON loc.location_id = n.location_id\n" + "WHERE n.node_id = id_from_vos_path(?)\n"; Loading Loading @@ -286,8 +287,9 @@ public class NodeDAO { Boolean isBusy = rs.getBoolean("busy_state"); Boolean isPermissionDenied = rs.getBoolean("is_permission_denied"); Boolean isSticky = rs.getBoolean("is_sticky"); Boolean isImmutable = rs.getBoolean("is_immutable"); ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy, isPermissionDenied, isSticky); ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy, isPermissionDenied, isSticky, isImmutable); return Optional.of(result); }); Loading Loading @@ -404,6 +406,30 @@ public class NodeDAO { }); } public void setBranchImmutable(Long rootNodeId, boolean setImmutable) { String sql = "UPDATE node c SET immutable = ? " + "FROM node r " + "WHERE r.node_id = ? " + "AND r.path @> c.path"; jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement(sql); ps.setBoolean(1, setImmutable); ps.setLong(2, rootNodeId); return ps; }); } public boolean isBranchImmutable(long parentNodeId) { String sql = "SELECT COUNT(c.node_id) > 0 " + "FROM node n " + "JOIN node c ON c.path <@ n.path " + "WHERE n.node_id = ? AND c.immutable IS TRUE"; return jdbcTemplate.queryForObject(sql, new Object[]{parentNodeId}, new int[]{Types.BIGINT}, Boolean.class); } public void releaseBusyNodesByJobId(String jobId) { String sql = "UPDATE node SET job_id = NULL WHERE job_id = ?"; Loading Loading @@ -742,14 +768,16 @@ public class NodeDAO { private final boolean busy; private final boolean permissionDenied; private final boolean sticky; private final boolean immutable; public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy, boolean permissionDenied, boolean sticky) { public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy, boolean permissionDenied, boolean sticky, boolean immutable) { this.nodeLtreePath = nodeLtreePath; this.container = container; this.writable = writable; this.busy = busy; this.permissionDenied = permissionDenied; this.sticky = sticky; this.immutable = immutable; } public String getDestinationNodeLtreePath() { Loading @@ -760,6 +788,10 @@ public class NodeDAO { return container; } public boolean isImmutable() { return immutable; } public boolean isWritable() { return writable; } Loading