Skip to content
TransferController.java 9.27 KiB
Newer Older
Sara Bertocco's avatar
Sara Bertocco committed
package it.inaf.oats.vospace;

import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.persistence.JobDAO;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.Jobs;
Sara Bertocco's avatar
Sara Bertocco committed
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
Sara Bertocco's avatar
Sara Bertocco committed
import org.springframework.web.bind.annotation.RestController;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import net.ivoa.xml.uws.v1.ErrorSummary;
Sara Bertocco's avatar
Sara Bertocco committed

@RestController
public class TransferController {

    @Autowired
    private JobDAO jobDAO;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private JobService jobService;

    @PostMapping(value = "/transfers",
            consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
            produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<?> postAsyncTransfer(@RequestBody Transfer transfer,
            @RequestParam(value = "PHASE", required = false) Optional<String> phase, User principal) {
        JobSummary jobSummary = newJobSummary(transfer, principal);

        jobDAO.createJob(jobSummary);

        if (phase.isPresent()) {
            jobService.setJobPhase(jobSummary, phase.get());
        return getJobRedirect(jobSummary.getJobId());
    }

    @PostMapping(value = "/synctrans",
            consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
            produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<?> postSyncTransfer(@RequestBody Transfer transfer, User principal) {

        JobSummary jobSummary = newJobSummary(transfer, principal);

        jobService.createSyncJobResult(jobSummary);

        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", request.getContextPath() + "/transfers/" + jobSummary.getJobId() + "/results/transferDetails");
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
    @GetMapping("/synctrans")
    public ResponseEntity<?> syncTransferUrlParamsMode(@RequestParam("TARGET") String target,
            @RequestParam("DIRECTION") String direction, @RequestParam("PROTOCOL") String protocolUris, User principal) {

        Transfer transfer = new Transfer();
        transfer.setTarget(target);
        transfer.setDirection(direction);

        // CADC client sends multiple protocol parameters and Spring join them using a comma separator.
        // Some values seems to be duplicated, so a set is used to avoid the duplication
        for (String protocolUri : new HashSet<>(Arrays.asList(protocolUris.split(",")))) {
            Protocol protocol = new Protocol();
            protocol.setUri(protocolUri);
            transfer.getProtocols().add(protocol);
        }

        JobSummary jobSummary = newJobSummary(transfer, principal);
        jobService.createSyncJobResult(jobSummary);

        if (jobSummary.getErrorSummary() != null) {
            // TODO: decide how to hanlde HTTP error codes
            // If an error occurs with the synchronous convenience mode where the preferred endpoint
            // is immediately returned as a redirect, the error information is returned directly in 
            // the response body with the associated HTTP status code. 
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(jobSummary.getErrorSummary().getMessage());
        }
        // Behaves as if REQUEST=redirect was set, for compatibility with CADC client
        String endpoint = transfer.getProtocols().get(0).getEndpoint();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", endpoint);
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
    }

    @GetMapping(value = "/transfers/{jobId}", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<JobSummary> getJob(@PathVariable("jobId") String jobId) {
        return jobDAO.getJob(jobId).map(j -> ResponseEntity.ok(j)).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping(value = "/transfers/{jobId}/error", produces = {MediaType.TEXT_PLAIN_VALUE})
    public ResponseEntity<String> getJobError(@PathVariable("jobId") String jobId) {
        return jobDAO.getJob(jobId).map(j -> {
            if (j.getPhase().equals(ExecutionPhase.ERROR)) {

                ErrorSummary e = j.getErrorSummary();

                if (e.isHasDetail()) {
                    return ResponseEntity.ok(e.getDetailMessage());
                } else {
                    return ResponseEntity.ok("No error details available");
                }
            } else {
                return ResponseEntity.ok("Job is not in ERROR phase");
            }
        }).orElse(ResponseEntity.notFound().build());
    }

    @PostMapping(value = "/transfers/{jobId}/phase")
    public ResponseEntity<?> setJobPhase(@PathVariable("jobId") String jobId, @RequestParam("PHASE") String phase, User principal) {

        return jobDAO.getJob(jobId).map(job -> {
            if (!job.getOwnerId().equals(principal.getName())) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
            }

            jobService.setJobPhase(job, phase);

            return getJobRedirect(job.getJobId());

        }).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping(value = "/transfers/{jobId}/results/transferDetails", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<?> getTransferDetails(@PathVariable("jobId") String jobId, User principal) {

        return jobDAO.getJob(jobId).map(job -> {
            if (!job.getOwnerId().equals(principal.getName())) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
            // TODO: check type
            return ResponseEntity.ok((Transfer) (job.getJobInfo().getAny().get(0)));

        }).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping(value = "/transfers/{jobId}/phase", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<?> getTransferJobPhase(@PathVariable("jobId") String jobId, User principal) {
        // TODO: error handling
        return ResponseEntity.ok(jobDAO.getJob(jobId).get().getPhase().toString());
    }

    @GetMapping(value = "/transfers", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<?> getTransfers(
            @RequestParam(value = "PHASE", required = false) Optional<List<ExecutionPhase>> phase,
            @RequestParam(value = "AFTER", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Optional<LocalDateTime> after,
            @RequestParam(value = "LAST", required = false) Optional<Integer> last,
            @RequestParam(value = "direction", required = false) Optional<List<JobService.JobDirection>> direction,
            User principal) {

        if (last.isPresent()) {
            if (last.get() <= 0) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }

        String userId = principal.getName();

        List<ExecutionPhase> phaseList;
        if (phase.isPresent()) {
            phaseList = phase.get();
        } else {
            phaseList = List.of();
        }

        List<JobService.JobDirection> directionList;
        if (direction.isPresent()) {
            directionList = direction.get();
        } else {
            directionList = List.of();

        Jobs jobs = jobDAO.getJobs(userId, phaseList, directionList, after, last);

        return ResponseEntity.ok(jobs);
    }

    private JobSummary newJobSummary(Transfer transfer, User principal) {
        String jobId = UUID.randomUUID().toString().replace("-", "");
        JobSummary jobSummary = new JobSummary();
        jobSummary.setJobId(jobId);
        jobSummary.setOwnerId(principal.getName());
        jobSummary.setPhase(ExecutionPhase.PENDING);
        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
        jobInfo.getAny().add(transfer);
        jobSummary.setJobInfo(jobInfo);

        return jobSummary;
    }

    private ResponseEntity<?> getJobRedirect(String jobId) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", request.getContextPath() + "/transfers/" + jobId);
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
Sara Bertocco's avatar
Sara Bertocco committed
    }