Commit 30c3bb86 authored by Nicola Fulvio Calabria's avatar Nicola Fulvio Calabria
Browse files

Merge branch 'immutable' into 'master'

Immutable

See merge request !1
parents 6622ea34 2df69c87
Loading
Loading
Loading
Loading
Loading
+11 −63
Original line number Diff line number Diff line
@@ -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;
@@ -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})
@@ -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);
        }

    }

}


    
    
+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);
                }

            }

        }
    }
}
+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
        
    }

}
+6 −0
Original line number Diff line number Diff line
@@ -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);
            }           
@@ -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();
                
+35 −3
Original line number Diff line number Diff line
@@ -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";
@@ -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);
        });
@@ -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 = ?";

@@ -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() {
@@ -760,6 +788,10 @@ public class NodeDAO {
            return container;
        }

        public boolean isImmutable() {
            return immutable;
        }

        public boolean isWritable() {
            return writable;
        }
Loading