Commit 82bda48c authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled tar/zip creation and download of resulting file

parent 3f92506f
Loading
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ package it.inaf.ia2.vospace.ui;
import it.inaf.ia2.aa.AuthConfig;
import it.inaf.ia2.aa.LoginFilter;
import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.ServletRapClient;
import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.gms.client.GmsClient;
import it.inaf.ia2.rap.client.ClientCredentialsRapClient;
@@ -86,4 +87,9 @@ public class VOSpaceUiApplication {
        String gmsBaseUri = ServiceLocator.getInstance().getConfig().getGmsUri();
        return new GmsClient(gmsBaseUri);
    }

    @Bean
    public ServletRapClient servletRapClient() {
        return (ServletRapClient) ServiceLocator.getInstance().getRapClient();
    }
}
+30 −3
Original line number Diff line number Diff line
@@ -175,9 +175,20 @@ public class VOSpaceClient {
        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class));
    }

    public List<Job> getJobs() {
    public List<Job> getAsyncRecallJobs() {
        return getJobs("direction=pullToVoSpace", Job.JobType.ASYNC_RECALL);
    }

    public List<Job> getArchiveJobs() {
        return getJobs("direction=pullFromVoSpace"
                + "&VIEW=ivo://ia2.inaf.it/vospace/views%23tar"
                + "&VIEW=ivo://ia2.inaf.it/vospace/views%23zip",
                Job.JobType.ARCHIVE);
    }

    private List<Job> getJobs(String queryString, Job.JobType type) {

        HttpRequest request = getRequest("/transfers?direction=pullToVoSpace")
        HttpRequest request = getRequest("/transfers?" + queryString)
                .header("Accept", useJson ? "application/json" : "text/xml")
                .header("Content-Type", useJson ? "application/json" : "text/xml")
                .GET()
@@ -185,11 +196,27 @@ public class VOSpaceClient {

        return call(request, BodyHandlers.ofInputStream(), 200, res -> {
            return unmarshal(res, Jobs.class).getJobref().stream()
                    .map(jobDesc -> new Job(jobDesc))
                    .map(jobDesc -> new Job(jobDesc, type))
                    .collect(Collectors.toList());
        });
    }

    public String getArchiveJobHref(String jobId) {
        List<Protocol> protocols = getTransferDetails(jobId).getProtocols();
        if (!protocols.isEmpty()) {
            return protocols.get(0).getEndpoint();
        }
        return null;
    }

    private Transfer getTransferDetails(String jobId) {

        HttpRequest request = getRequest("/transfers/" + jobId + "/results/transferDetails")
                .GET().build();

        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class));
    }

    public ExecutionPhase getJobPhase(String jobId) {

        HttpRequest request = getRequest("/transfers/" + jobId + "/phase")
+96 −0
Original line number Diff line number Diff line
/*
 * This file is part of vospace-ui
 * Copyright (C) 2021 Istituto Nazionale di Astrofisica
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package it.inaf.ia2.vospace.ui.controller;

import it.inaf.ia2.aa.ServletRapClient;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.rap.client.call.TokenExchangeRequest;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.exception.PermissionDeniedException;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
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.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DownloadController {

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

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

    @Autowired
    private VOSpaceClient client;

    @Autowired
    private HttpServletRequest servletRequest;

    @Autowired
    private ServletRapClient rapClient;

    @GetMapping(value = "/download/**")
    public ResponseEntity<?> directDownload() {

        String path = getPath("/download/");
        LOG.debug("directDownload called for path {}", path);

        Transfer transfer = new Transfer();
        transfer.setDirection("pullFromVoSpace");
        transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path)));

        Protocol protocol = new Protocol();
        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
        transfer.getProtocols().add(protocol);

        String url = client.getFileServiceEndpoint(transfer);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", url);
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
    }

    @GetMapping(value = "/download")
    public ResponseEntity<?> downloadJobResult(@RequestParam("jobId") String jobId) {

        LOG.debug("job result download called for jobId {}", jobId);

        String token = ((User) servletRequest.getUserPrincipal()).getAccessToken();

        if (token == null) {
            throw new PermissionDeniedException("Token is null");
        }

        String endpoint = client.getArchiveJobHref(jobId);

        TokenExchangeRequest exchangeRequest = new TokenExchangeRequest()
                .setSubjectToken(token)
                .setResource(endpoint);

        String newToken = rapClient.exchangeToken(exchangeRequest, servletRequest);

        String url = endpoint + "?token=" + newToken;
        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", url);
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
    }

    protected String getPath(String prefix) {
        String requestURL = servletRequest.getRequestURL().toString();
        return NodeUtils.getPathFromRequestURLString(requestURL, prefix);
    }
}
+22 −5
Original line number Diff line number Diff line
@@ -8,7 +8,9 @@ package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.Job;
import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import net.ivoa.xml.uws.v1.ErrorType;
import net.ivoa.xml.uws.v1.ExecutionPhase;
@@ -59,7 +61,7 @@ public class JobController extends BaseController {
        if (job.getPhase() == ExecutionPhase.QUEUED
                || job.getPhase() == ExecutionPhase.PENDING
                || job.getPhase() == ExecutionPhase.EXECUTING) {
            return ResponseEntity.ok(new Job(job));
            return ResponseEntity.ok(new Job(job, Job.JobType.ASYNC_RECALL));
        }
        String errorMessage;
        if (job.getPhase() == ExecutionPhase.ERROR) {
@@ -87,7 +89,22 @@ public class JobController extends BaseController {
    }

    @GetMapping(value = "/jobs", produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Job> getJobs() {
        return client.getJobs();
    public List<Job> getJobs() throws Exception {

        CompletableFuture<List<Job>> asyncRecallJobsCall
                = CompletableFuture.supplyAsync(() -> client.getAsyncRecallJobs(),
                        Runnable::run);

        CompletableFuture<List<Job>> archiveJobsCall
                = CompletableFuture.supplyAsync(() -> client.getArchiveJobs(),
                        Runnable::run);

        CompletableFuture.allOf(asyncRecallJobsCall, archiveJobsCall).join();

        List<Job> jobs = new ArrayList<>();
        jobs.addAll(asyncRecallJobsCall.get());
        jobs.addAll(archiveJobsCall.get());

        return jobs;
    }
}
+67 −25
Original line number Diff line number Diff line
@@ -25,15 +25,15 @@ import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Param;
import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
import net.ivoa.xml.vospace.v2.View;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -94,26 +94,6 @@ public class NodesController extends BaseController {
        return ResponseEntity.ok(listNodeData);
    }

    @GetMapping(value = "/download/**")
    public ResponseEntity<?> directDownload() {

        String path = getPath("/download/");
        LOG.debug("directDownload called for path {}", path);

        Transfer transfer = new Transfer();
        transfer.setDirection("pullFromVoSpace");
        transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path)));

        Protocol protocol = new Protocol();
        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
        transfer.getProtocols().add(protocol);

        String url = client.getFileServiceEndpoint(transfer);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", url);
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
    }

    @PostMapping(value = "/folder")
    public void newFolder(@RequestBody Map<String, Object> params) {

@@ -162,6 +142,68 @@ public class NodesController extends BaseController {
        return ResponseEntity.noContent().build();
    }

    @PostMapping(value = "/zip", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Job createZip(@RequestBody List<String> paths) {
        return createArchive(paths, "ivo://ia2.inaf.it/vospace/views#zip");
    }

    @PostMapping(value = "/tar", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Job createTar(@RequestBody List<String> paths) {
        return createArchive(paths, "ivo://ia2.inaf.it/vospace/views#tar");
    }

    private Job createArchive(List<String> paths, String viewUri) {

        Transfer transfer = new Transfer();

        View view = new View();
        view.setUri(viewUri);
        transfer.setView(view);

        if (paths.size() == 1) {
            transfer.setTarget(Arrays.asList("vos://" + authority + paths.get(0)));
        } else {
            String parent = getCommonParent(paths);
            transfer.setTarget(Arrays.asList("vos://" + authority + parent));
            for (String path : paths) {
                String childName = path.substring(parent.length() + 1);
                Param param = new Param();
                param.setUri(viewUri + "/include");
                param.setValue(childName);
                view.getParam().add(param);
            }
        }

        transfer.setDirection("pullFromVoSpace");
        Protocol protocol = new Protocol();
        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
        transfer.getProtocols().add(protocol);

        return new Job(client.startTransferJob(transfer), Job.JobType.ARCHIVE);
    }

    private String getCommonParent(List<String> vosPaths) {
        String commonParent = null;
        for (String vosPath : vosPaths) {
            if (commonParent == null) {
                commonParent = vosPath;
            } else {
                StringBuilder newCommonParent = new StringBuilder();
                boolean same = true;
                int lastSlashPos = vosPath.lastIndexOf("/");
                for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()) && i < lastSlashPos; i++) {
                    if (commonParent.charAt(i) == vosPath.charAt(i)) {
                        newCommonParent.append(commonParent.charAt(i));
                    } else {
                        same = false;
                    }
                }
                commonParent = newCommonParent.toString();
            }
        }
        return commonParent;
    }

    @PostMapping(value = "/move")
    public ResponseEntity<Job> moveNode(@RequestBody Map<String, Object> params) {

@@ -195,7 +237,7 @@ public class NodesController extends BaseController {

        job.setPhase(phase);

        return ResponseEntity.ok(new Job(job));
        return ResponseEntity.ok(new Job(job, Job.JobType.MOVE));
    }

    protected String getPath(String prefix) {
Loading