Skip to content
FileCopyService.java 7.62 KiB
Newer Older
/*
 * This file is part of vospace-file-service
 * Copyright (C) 2021 Istituto Nazionale di Astrofisica
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package it.inaf.ia2.transfer.service;

import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.LocationDAO;
import it.inaf.ia2.transfer.persistence.model.FileInfo;
Nicola Fulvio Calabria's avatar
Nicola Fulvio Calabria committed
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class FileCopyService {

    private static final Logger LOG = LoggerFactory.getLogger(FileCopyService.class);

    @Autowired
    private FileDAO fileDAO;

    @Autowired
    private LocationDAO locationDAO;

    @Autowired
    private AuthorizationService authorizationService;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${upload_location_id}")
    private int uploadLocationId;

    public void copyFiles(String sourceRootVosPath,
            String destinationRootVosPath, String jobId, TokenPrincipal principal) {
        // We use jobId to identify nodes created by the REST part of CopyNode
        // We expect them to be locked

        List<FileInfo> sources
                = fileDAO.getBranchFileInfos(sourceRootVosPath, jobId);
        // Set location of destinations to this file service update location 
        // before retrieving file infos

        fileDAO.setBranchLocationId(destinationRootVosPath, jobId, uploadLocationId);
        List<FileInfo> destinations
                = fileDAO.getBranchFileInfos(destinationRootVosPath, jobId);

        if (sources.size() != destinations.size()) {
            throw new IllegalStateException("Sources and destinations list have different sizes");
        }

        // Create destination directories on disk
        this.makeDirectoryStructure(destinations);

        this.fillDestinations(destinations,
                sources,
                sourceRootVosPath,
                destinationRootVosPath,
                principal);

    }

    private void makeDirectory(FileInfo containerFileInfo) {
        File file = new File(containerFileInfo.getOsPath());

        if (!file.exists()) {
            if (!file.mkdirs()) {
                throw new IllegalStateException("Unable to create directory " + containerFileInfo.getOsPath());
            }
        }

    }

    private void makeDirectoryStructure(List<FileInfo> destinationFileInfos) {
        for (FileInfo f : destinationFileInfos) {
            if (f.isDirectory()) {
                this.makeDirectory(f);
            }
        }
    }

    private void fillDestinations(List<FileInfo> destinationFileInfos,
            List<FileInfo> sourcesFileInfos,
            String sourceRootVosPath,
            String destinationRootVosPath,
            TokenPrincipal principal) {

        // it will be initialized only when necessary
        Map<Integer, String> portalLocationUrls = null;

        for (FileInfo f : destinationFileInfos) {
            if (!f.isDirectory()) {
                // Calculate source file vos path
                String correspondingSourceVosPath
                        = this.getCorrespondingSourceVosPath(
                                sourceRootVosPath,
                                destinationRootVosPath,
                                f.getVirtualPath());

                Optional<FileInfo> sourceOpt = this.findFileInfoByVosPath(sourcesFileInfos,
                        correspondingSourceVosPath);

                FileInfo source = sourceOpt
                        .orElseThrow(() -> new IllegalStateException("Can't find file info for: "
                        + correspondingSourceVosPath + " in source files list"));

                if (source.getLocationId() != null && source.getLocationId() != uploadLocationId) {
                    // remote file
                    if (portalLocationUrls == null) {
                        portalLocationUrls = locationDAO.getPortalLocationUrls();
                    }
                    String url = portalLocationUrls.get(source.getLocationId());
                    // download file to destination disk path
                    this.downloadFileToDisk(source, f, principal, url);

                } else {
                    // local file
                    // copy file to destination disk path
                    this.copyLocalFile(source, f, principal);

                }

            }
        }

    }

    private String getCorrespondingSourceVosPath(String sourceRootVosPath,
            String destinationRootVosPath,
            String destinationVosPath) {
        return sourceRootVosPath
                + destinationVosPath.substring(destinationRootVosPath.length());
    }

    private Optional<FileInfo> findFileInfoByVosPath(List<FileInfo> list, String vosPath) {

        return list.stream().filter(i -> i.getVirtualPath().equals(vosPath)).findFirst();

    }

    private void downloadFileToDisk(FileInfo sourceFile,
            FileInfo destinationFile, TokenPrincipal tokenPrincipal, String baseUrl) {

        if (baseUrl == null) {
            LOG.error("Location URL not found for location " + sourceFile.getLocationId());
Nicola Fulvio Calabria's avatar
Nicola Fulvio Calabria committed
            throw new InternalFaultException("Unable to retrieve location of file " + sourceFile.getVirtualPath());
        }

        String url = baseUrl + "/" + sourceFile.getVirtualName();

        LOG.trace("Downloading file from " + url);

        restTemplate.execute(url, HttpMethod.GET, req -> {
            HttpHeaders headers = req.getHeaders();
            if (tokenPrincipal.getToken() != null) {
                headers.setBearerAuth(tokenPrincipal.getToken());
            }
        }, res -> {

            File outFile = new File(destinationFile.getOsPath());

            try (FileOutputStream os = new FileOutputStream(outFile)) {
                res.getBody().transferTo(os);
            } catch (Exception e) {
                outFile.delete();
                throw e;
            }

            return null;
        }, new Object[]{});
    }

    private void copyLocalFile(FileInfo sourceFileInfo,
            FileInfo destinationFileInfo, TokenPrincipal tokenPrincipal) {
        if (!authorizationService.isDownloadable(sourceFileInfo, tokenPrincipal)) {
Nicola Fulvio Calabria's avatar
Nicola Fulvio Calabria committed
            throw PermissionDeniedException.forPath(sourceFileInfo.getVirtualPath());
        }

        File file = new File(sourceFileInfo.getOsPath());
        LOG.trace("Copying file: " + file.getAbsolutePath() + " to: "
                + destinationFileInfo.getOsPath());

        File sourceFile = new File(sourceFileInfo.getOsPath());
        File destinationFile = new File(destinationFileInfo.getOsPath());

        try {
            Files.copy(sourceFile.toPath(), destinationFile.toPath());
        } catch (IOException e) {
            if (Files.exists(destinationFile.toPath())) {
                destinationFile.delete();
            }

            throw new UncheckedIOException(e);
        } catch (Exception e) {
            if (Files.exists(destinationFile.toPath())) {
                destinationFile.delete();
            }

            throw e;

        }

    }
}