diff --git a/src/main/java/it/inaf/oats/vospace/TransferController.java b/src/main/java/it/inaf/oats/vospace/TransferController.java index 4b1e68ceea02b804a65d39fc88f04cab58591b04..1618fed28bb3bc07993c04cd8534edd4cab2c402 100644 --- a/src/main/java/it/inaf/oats/vospace/TransferController.java +++ b/src/main/java/it/inaf/oats/vospace/TransferController.java @@ -1,18 +1,90 @@ package it.inaf.oats.vospace; +import it.inaf.ia2.aa.data.User; +import it.inaf.oats.vospace.persistence.JobDAO; +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.vospace.v2.Transfer; +import org.springframework.beans.factory.annotation.Autowired; +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.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TransferController { - @PostMapping(value = "/transfer", + @Autowired + private JobDAO jobDAO; + + @Autowired + private HttpServletRequest request; + + @Autowired + private UriService uriService; + + @PostMapping(value = "/transfers", consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, - produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) - public Transfer postTransfer(@RequestBody Transfer transfer) { - return transfer; + produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity<?> postTransfer(@RequestBody 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); + + jobDAO.createJob(jobSummary); + + return getJobRedirect(jobId); + } + + @GetMapping(value = "/transfers/{jobId}", produces = {MediaType.APPLICATION_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()); + } + + @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(); + } + + // TODO: check allowed job phase transitions + switch (phase) { + case "RUN": + job.setPhase(ExecutionPhase.EXECUTING); + uriService.setTransferJobResult(job); + jobDAO.updateJob(job); + break; + case "ABORT": + throw new UnsupportedOperationException("Not implemented yet"); + default: + throw new IllegalArgumentException("Invalid phase parameter: " + phase); + } + + return getJobRedirect(job.getJobId()); + + }).orElse(ResponseEntity.notFound().build()); + } + + private ResponseEntity<?> getJobRedirect(String jobId) { + HttpHeaders headers = new HttpHeaders(); + headers.set("Location", request.getContextPath() + "/transfers/" + jobId); + return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } -} \ No newline at end of file +} diff --git a/src/main/java/it/inaf/oats/vospace/UriService.java b/src/main/java/it/inaf/oats/vospace/UriService.java new file mode 100644 index 0000000000000000000000000000000000000000..0d28a0ed44cb483cf0a808d99dcbf1107f89868e --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/UriService.java @@ -0,0 +1,52 @@ +package it.inaf.oats.vospace; + +import it.inaf.oats.vospace.persistence.NodeDAO; +import java.util.ArrayList; +import java.util.List; +import net.ivoa.xml.uws.v1.JobSummary; +import net.ivoa.xml.uws.v1.ResultReference; +import net.ivoa.xml.vospace.v2.Node; +import net.ivoa.xml.vospace.v2.Transfer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class UriService { + + @Value("${vospace-authority}") + private String authority; + + @Value("${file-service-url}") + private String fileServiceUrl; + + @Autowired + private NodeDAO nodeDao; + + public void setTransferJobResult(JobSummary job) { + + List<ResultReference> results = new ArrayList<>(); + + ResultReference result = new ResultReference(); + result.setHref(getUri(job)); + results.add(result); + + job.setResults(results); + } + + private String getUri(JobSummary job) { + + // TODO add checks on data type + Transfer transfer = (Transfer) job.getJobInfo().getAny().get(0); + + String relativePath = transfer.getTarget().substring("vos://".length() + authority.length()); + + // TODO handle node not found + Node node = nodeDao.listNode(relativePath).get(); + + // TODO build the path according to node type + // + // TODO add token for authenticated access + return fileServiceUrl + relativePath + "?jobId=" + job.getJobId(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b2697437b3ca10bdf9179f3c7f47b0dea7f31dc..aa0868511f095d90ae369c60372f4a698af7063b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,3 +34,5 @@ logging.level.org.springframework.web=TRACE #logging.file=path/to/log/file.log vospace-authority=example.com!vospace + +file-service-url=http://localhost:8087 \ No newline at end of file diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f401827d7a1aefdc72704beff23bf58b3e4e4e --- /dev/null +++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java @@ -0,0 +1,52 @@ +package it.inaf.oats.vospace; + +import static it.inaf.oats.vospace.VOSpaceXmlTestUtil.loadDocument; +import it.inaf.oats.vospace.persistence.JobDAO; +import java.util.Optional; +import net.ivoa.xml.uws.v1.JobSummary; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +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.http.MediaType; +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.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.w3c.dom.Document; + +@SpringBootTest +@AutoConfigureMockMvc +public class TransferControllerTest { + + @MockBean + private JobDAO dao; + + @Autowired + private MockMvc mockMvc; + + @Test + public void testGetJob() throws Exception { + + JobSummary job = new JobSummary(); + + when(dao.getJob(eq("123"))).thenReturn(Optional.of(job)); + + String xml = mockMvc.perform(get("/transfers/123") + .accept(MediaType.APPLICATION_XML)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + Document doc = loadDocument(xml); + assertEquals("uws:job", doc.getDocumentElement().getNodeName()); + + verify(dao, times(1)).getJob(eq("123")); + } +} diff --git a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7112eac8c679b40cec8d6d5b6f4a4110215c86c4 --- /dev/null +++ b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java @@ -0,0 +1,52 @@ +package it.inaf.oats.vospace; + +import it.inaf.oats.vospace.persistence.NodeDAO; +import java.util.Optional; +import net.ivoa.xml.uws.v1.JobSummary; +import net.ivoa.xml.vospace.v2.DataNode; +import net.ivoa.xml.vospace.v2.Node; +import net.ivoa.xml.vospace.v2.Transfer; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +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.context.TestPropertySource; + +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(properties = {"vospace-authority=example.com!vospace", "file-service-url=http://file-service"}) +public class UriServiceTest { + + @MockBean + private NodeDAO dao; + + @Autowired + private UriService transferService; + + @Test + public void testSimpleUrl() { + + Node node = new DataNode(); + + when(dao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); + + Transfer transfer = new Transfer(); + transfer.setTarget("vos://example.com!vospace/mydata1"); + + JobSummary job = new JobSummary(); + job.setJobId("job-id"); + + JobSummary.JobInfo jobInfo = new JobSummary.JobInfo(); + jobInfo.getAny().add(transfer); + + job.setJobInfo(jobInfo); + + transferService.setTransferJobResult(job); + + assertEquals("http://file-service/mydata1?jobId=job-id", job.getResults().get(0).getHref()); + } +}