Commit 6a55aef3 authored by Nicola Fulvio Calabria's avatar Nicola Fulvio Calabria
Browse files

Partial implementation of copy node

parent 0dc0489f
Loading
Loading
Loading
Loading
+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.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
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 it.inaf.oats.vospace.persistence.NodeDAO.ShortNodeDescriptor;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.CannotSerializeTransactionException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
@EnableTransactionManagement
public class CopyService {

    @Autowired
    private NodeDAO nodeDao;

    @Value("${vospace-authority}")
    private String authority;

    @Autowired
    private HttpServletRequest servletRequest;

    @Autowired
    private NodeBranchService nodeBranchService;

    public void processCopyJob(Transfer transfer, String jobId) {
        if (transfer.getTarget().size() != 1) {
            throw new InvalidArgumentException("Invalid target size for copyNode: " + transfer.getTarget().size());
        }

        // Get Source Vos Path
        String sourcePath = URIUtils.returnVosPathFromNodeURI(transfer.getTarget().get(0), authority);

        // Get Destination Vos Path (it's in transfer direction)
        String destinationPath = URIUtils.returnVosPathFromNodeURI(transfer.getDirection(), authority);

        // Extract User permissions from servlet request
        User user = (User) servletRequest.getUserPrincipal();

        this.validatePath(sourcePath);
        this.validatePath(destinationPath);

        if (sourcePath.equals(destinationPath)) {
            return;
        }

        // check source branch for read and lock it
        nodeBranchService.checkBranchForReadAndLock(sourcePath, jobId);

    }

    private void validatePath(String path) {
        if (path.equals("/")) {
            throw new IllegalArgumentException("Cannot move root node or to root node");
        }
    }

}
+19 −7
Original line number Diff line number Diff line
@@ -33,6 +33,9 @@ public class JobService {
    @Autowired
    private MoveService moveService;
    
    @Autowired
    private CopyService copyService;

    @Autowired
    private AsyncTransferService asyncTransfService;

@@ -108,6 +111,10 @@ public class JobService {
                case moveNode:
                    handleMoveNode(transfer);
                    break;
                case copyNode:
                    handleCopyNode(transfer, job.getJobId());
                    break;

                default:
                    throw new UnsupportedOperationException("Not implemented yet");
            }
@@ -152,11 +159,16 @@ public class JobService {
        uriService.setTransferJobResult(job, transfer);
    }

    private void handleMoveNode(Transfer transfer)
    {
    private void handleMoveNode(Transfer transfer) {
        moveService.processMoveJob(transfer);
    }
    
    private void handleCopyNode(Transfer transfer, String jobId)
    {
        copyService.processCopyJob(transfer, jobId);
    }
    

    private JobDirection getJobDirection(Transfer transfer) {
        return JobDirection.getJobDirectionEnumFromTransfer(transfer);
    }
+71 −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.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
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 it.inaf.oats.vospace.persistence.NodeDAO.ShortNodeDescriptor;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.CannotSerializeTransactionException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
@EnableTransactionManagement
public class NodeBranchService {

    @Autowired
    private NodeDAO nodeDao;

    @Autowired
    private HttpServletRequest servletRequest;

    @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ)
    public void checkBranchForReadAndLock(String sourcePath, String jobId) {

        // Extract User permissions from servlet request
        User user = (User) servletRequest.getUserPrincipal();

        try {
            // Get source node
            Optional<Long> sourceIdOpt = nodeDao.getNodeId(sourcePath);
            long sourceId = sourceIdOpt.orElseThrow(() -> new NodeNotFoundException(sourcePath));

            if (nodeDao.isBranchBusy(sourceId)) {
                throw new NodeBusyException(sourcePath);
            }

            if (!nodeDao.isBranchReadable(sourceId, user.getName(), user.getGroups())) {
                throw new PermissionDeniedException(sourcePath);
            }
            
            this.lockBranch(sourceId, jobId);

        } catch (CannotSerializeTransactionException ex) {
            // Concurrent transactions attempted to modify this set of nodes            
            throw new NodeBusyException(sourcePath);
        }
    }

    private void lockBranch(Long sourceRootId, String jobId) {

        nodeDao.setBranchJobId(sourceRootId, jobId);

    }   

}
+87 −0
Original line number Diff line number Diff line
@@ -320,6 +320,51 @@ public class NodeDAO {
        });
    }

    public void copySingleNode(Long sourceId,
            Long destId, 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 = ?";

        // 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 = ?";

        // 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;

        jdbcTemplate.update(conn -> {
            PreparedStatement ps = conn.prepareStatement(cteSQL);
            ps.setLong(1, sourceId);
            ps.setLong(2, destId);
            ps.setString(3, jobId);
            return ps;
        });

    }

    public boolean isBranchBusy(long parentNodeId) {

        String sql = "SELECT COUNT(c.node_id) > 0 "
@@ -330,6 +375,18 @@ public class NodeDAO {
        return jdbcTemplate.queryForObject(sql, new Object[]{parentNodeId}, new int[]{Types.BIGINT}, Boolean.class);
    }

    public void setBranchJobId(Long rootNodeId, String jobId) {
        String sql = "UPDATE node SET job_id = ?\n"
                + "WHERE path ~ ('*.' || ? || '.*')::lquery";

        jdbcTemplate.update(conn -> {
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, jobId);
            ps.setLong(2, rootNodeId);
            return ps;
        });
    }

    public boolean isBranchWritable(long parentNodeId, String userId, List<String> userGroups) {

        String sql = "SELECT COUNT(c.node_id) = 0 "
@@ -361,6 +418,36 @@ public class NodeDAO {
        });
    }

    public boolean isBranchReadable(long parentNodeId, String userId, List<String> userGroups) {

        String sql = "SELECT COUNT(c.node_id) = 0 "
                + "FROM node n "
                + "JOIN node c ON c.path <@ n.path "
                + "WHERE n.node_id = ? "
                + "(NOT COALESCE(c.is_public, FALSE) "
                + "AND (SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(c.group_read)) AS allowed_groups) = 0 "
                + "AND c.creator_id <> ?";

        return jdbcTemplate.query(sql, ps -> {
            ps.setLong(1, parentNodeId);

            String[] groups;
            if (userGroups == null) {
                groups = new String[0];
            } else {
                groups = userGroups.toArray(String[]::new);
            }
            ps.setArray(2, ps.getConnection().createArrayOf("varchar", groups));

            ps.setString(3, userId);
        }, row -> {
            if (!row.next()) {
                throw new IllegalStateException("Expected one result");
            }
            return row.getBoolean(1);
        });
    }

    public void deleteNode(String path) {
        int nodesWithPath = countNodesWithPath(path);
        if (nodesWithPath == 0) {