From 305d2e29551e2bfbe47b7ae587c8fda524d24bbf Mon Sep 17 00:00:00 2001 From: Sonia Zorba <sonia.zorba@inaf.it> Date: Sat, 9 Jan 2021 10:27:50 +0100 Subject: [PATCH] Implemented download --- .../ia2/vospace/ui/client/VOSpaceClient.java | 13 +++++ .../vospace/ui/controller/JobController.java | 2 +- .../ui/controller/NodesController.java | 57 +++++++++++++++---- .../ui/controller/NodesControllerTest.java | 16 +++--- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java index 9cfa265..c7bb9c4 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java @@ -14,6 +14,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; +import java.util.List; import java.util.Scanner; import java.util.concurrent.CompletionException; import java.util.concurrent.ForkJoinPool; @@ -23,6 +24,7 @@ import javax.servlet.http.HttpSession; import javax.xml.bind.JAXB; import net.ivoa.xml.uws.v1.JobSummary; import net.ivoa.xml.vospace.v2.Node; +import net.ivoa.xml.vospace.v2.Protocol; import net.ivoa.xml.vospace.v2.Transfer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,6 +84,17 @@ public class VOSpaceClient { return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, JobSummary.class)); } + public List<Protocol> getDownloadEndpoints(Transfer transfer) { + + HttpRequest request = getRequest("/synctrans") + .header("Accept", useJson ? "application/json" : "text/xml") + .header("Content-Type", useJson ? "application/json" : "text/xml") + .POST(HttpRequest.BodyPublishers.ofString(marshal(transfer))) + .build(); + + return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class)).getProtocols(); + } + private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, Function<T, U> responseHandler) { try { return httpClient.sendAsync(request, responseBodyHandler) diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java index 5ef1a00..904a26b 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java @@ -38,7 +38,7 @@ public class JobController { transfer.setTarget("vos://" + authority + paths.get(0)); Protocol protocol = new Protocol(); protocol.setUri("ia2:tape-recall"); - transfer.getProtocol().add(protocol); + transfer.getProtocols().add(protocol); JobSummary job = client.startTransferJob(transfer); diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java index af16e52..199fbfc 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java @@ -1,19 +1,33 @@ package it.inaf.ia2.vospace.ui.controller; +import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.service.NodesService; 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.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class NodesController { + @Value("${vospace-authority}") + private String authority; + @Autowired private NodesService nodesService; + @Autowired + private VOSpaceClient client; + + @Autowired + private HttpServletRequest servletRequest; + /** * This is the only API endpoint that returns HTML code instead of JSON. The * reason is that JavaScript frameworks are not very efficient in handling @@ -23,21 +37,42 @@ public class NodesController { * document. */ @GetMapping(value = {"/nodes", "/nodes/**"}, produces = MediaType.TEXT_PLAIN_VALUE) - public String listNodes(HttpServletRequest request) throws Exception { + public String listNodes() throws Exception { + + String path = getPath("/nodes/"); + + return nodesService.generateNodesHtml(path); + } + + @GetMapping(value = "/download/**") + public ResponseEntity<?> directDownload() { - String requestURL = request.getRequestURL().toString(); - String[] split = requestURL.split("/nodes/"); + String path = getPath("/download/"); + + Transfer transfer = new Transfer(); + transfer.setDirection("pullFromVoSpace"); + transfer.setTarget("vos://" + authority + path); + + String url = client.getDownloadEndpoints(transfer).get(0).getEndpoint(); + HttpHeaders headers = new HttpHeaders(); + headers.set("Location", url); + return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); + } + + /** + * Slash is a special character in defining REST endpoints and trying to + * define a PathVariable containing slashes doesn't work, so the endpoint + * has been defined using "/nodes/**" instead of "/nodes/{path}" and the + * path is extracted manually parsing the request URL. + */ + protected String getPath(String prefix) { + String requestURL = servletRequest.getRequestURL().toString(); + String[] split = requestURL.split(prefix); String path = "/"; if (split.length == 2) { path += split[1]; } - - return nodesService.generateNodesHtml(path); - } - - @GetMapping(value = "/download/{path}") - public void directDownload(@PathVariable("path") String path) { - // TODO: call pullFromVoSpace sync transfer + return path; } } diff --git a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java index dc127f7..9bd05f7 100644 --- a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java +++ b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java @@ -3,26 +3,28 @@ package it.inaf.ia2.vospace.ui.controller; import it.inaf.ia2.vospace.ui.service.NodesService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import static org.mockito.ArgumentMatchers.eq; -import org.mockito.InjectMocks; -import org.mockito.Mock; import static org.mockito.Mockito.verify; -import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -@ExtendWith(MockitoExtension.class) +@SpringBootTest +@AutoConfigureMockMvc public class NodesControllerTest { - @Mock + @MockBean private NodesService nodesService; - @InjectMocks + @Autowired private NodesController controller; + @Autowired private MockMvc mockMvc; @BeforeEach -- GitLab