Skip to content
CopyService.java 4.65 KiB
Newer Older
/*
 * 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.NodeBusyException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.NodeDAO.ShortNodeDescriptor;
import java.util.Optional;
import net.ivoa.xml.vospace.v2.Transfer;
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 extends AbstractNodeService {
    @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ)
    public List<String> processCopyNodes(Transfer transfer, String jobId, User user) {

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

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

        // Destination source to be returned, null if no copy was performed
        String destinationCopyRoot = null;

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

        if (sourcePath.equals(destinationPath)) {
            throw new IllegalArgumentException("Cannot copy node to itself");
        }

        // Check if destination is subpath of source
        // Linux-like: "cannot copy to a subdirectory of itself" 
        if (destinationPath.startsWith(sourcePath + "/")) {
            throw new IllegalArgumentException("Cannot copy node to a subdirectory of its own path");
        // Check if destination equals parent path of source
        if (NodeUtils.getParentPath(sourcePath).equals(destinationPath)) {
            throw new IllegalArgumentException("Cannot duplicate node at same path without renaming it");
        try {

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

            // Check destination
            Optional<ShortNodeDescriptor> destShortNodeDescriptor
                    = nodeDao.getShortNodeDescriptor(destinationPath, user.getName(), user.getGroups());

            if (destShortNodeDescriptor.isPresent()) {
                this.validateDestinationContainer(destShortNodeDescriptor.get(), destinationPath);
                destinationCopyRoot = destinationPath + "/" + NodeUtils.getNodeName(sourcePath);

            } else {
                // Check if parent exists
                String destinationParentPath = NodeUtils.getParentPath(destinationPath);
                Optional<ShortNodeDescriptor> destShortNodeDescriptorParent
                        = nodeDao.getShortNodeDescriptor(destinationParentPath, user.getName(), user.getGroups());
                if (destShortNodeDescriptorParent.isPresent()) {
                    this.validateDestinationContainer(destShortNodeDescriptorParent.get(), destinationParentPath);
                    destinationCopyRoot = destinationPath;

                } else {
                    throw new UnsupportedOperationException("Creation of destination upon copy not supported");
                }

            }

        } catch (CannotSerializeTransactionException ex) {
            // Concurrent transactions attempted to modify this set of nodes            
            throw new NodeBusyException(sourcePath);
        }
        
        return List.of(sourcePath, destinationCopyRoot);
        
    private void checkBranchForReadAndLock(String sourcePath, String jobId, User user) {

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

        nodeDao.setBranchJobId(sourceId, jobId);

    }