diff --git a/src/main/java/it/inaf/oats/vospace/JobService.java b/src/main/java/it/inaf/oats/vospace/JobService.java index 9e90f41454dee187c805cf87cfbbdf65676978a3..749a4bac903298c81b599cff3fb7369b8ffd0a69 100644 --- a/src/main/java/it/inaf/oats/vospace/JobService.java +++ b/src/main/java/it/inaf/oats/vospace/JobService.java @@ -19,7 +19,7 @@ public class JobService { @Autowired private TapeService tapeService; - enum JobType { + public enum JobType { pullToVoSpace, pullFromVoSpace, pushToVoSpace, diff --git a/src/main/java/it/inaf/oats/vospace/TransferController.java b/src/main/java/it/inaf/oats/vospace/TransferController.java index 0ceaf4219f7516b9b3c730fdd1556dfd69519bad..c525baa345112de2b89623230fa7af1da296933f 100644 --- a/src/main/java/it/inaf/oats/vospace/TransferController.java +++ b/src/main/java/it/inaf/oats/vospace/TransferController.java @@ -7,6 +7,8 @@ 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; +import net.ivoa.xml.uws.v1.ShortJobDescription; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; @@ -19,6 +21,9 @@ 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; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import java.util.List; @RestController public class TransferController { @@ -103,6 +108,41 @@ public class TransferController { 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.JobType>> 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.JobType> 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("-", ""); diff --git a/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java index b3781a8d1be2456da9c4ba5669c34558df5de307..58a47e33ecfc732f9d8e049cd47ff822a969902a 100644 --- a/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java +++ b/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java @@ -3,19 +3,29 @@ package it.inaf.oats.vospace.persistence; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import java.sql.Timestamp; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.List; import java.util.Optional; import javax.sql.DataSource; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.JobSummary; +import net.ivoa.xml.uws.v1.ShortJobDescription; import net.ivoa.xml.uws.v1.ResultReference; +import net.ivoa.xml.uws.v1.Jobs; +import it.inaf.oats.vospace.JobService; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import java.util.ArrayList; +import java.time.LocalDateTime; +import java.math.BigDecimal; + @Repository public class JobDAO { @@ -105,6 +115,106 @@ public class JobDAO { return jobSummary; } + public Jobs getJobs(String userId, + List<ExecutionPhase> phaseList, + List<JobService.JobType> directionList, + Optional<LocalDateTime> after, + Optional<Integer> last + ) { + Jobs jobs = new Jobs(); + jobs.setVersion("1.1"); + + List<ShortJobDescription> sjdList = jobs.getJobref(); + // Query db to fill ShortJobDescription list + ArrayList<Object> queryParams = new ArrayList<>(); + ArrayList<Integer> queryParamTypes = new ArrayList<>(); + + StringBuilder sb = new StringBuilder(); + sb.append("SELECT * FROM job"); + + sb.append(" WHERE owner_id = ?"); + queryParams.add(userId); + queryParamTypes.add(Types.VARCHAR); + + // Fill conditions on execution phase + if (phaseList.isEmpty()) { + sb.append(" AND phase NOT IN (?)"); + queryParams.add(ExecutionPhase.ARCHIVED); + queryParamTypes.add(Types.OTHER); + } else { + sb.append(" AND phase IN ("); + for (int i = 0; i < phaseList.size(); i++) { + sb.append("?"); + queryParams.add(phaseList.get(i)); + queryParamTypes.add(Types.OTHER); + if (i < phaseList.size() - 1) { + sb.append(","); + } + } + sb.append(")"); + } + + // Fill conditions on type list + if (!directionList.isEmpty()) { + sb.append(" AND job_type IN ("); + for (int i = 0; i < directionList.size(); i++) { + sb.append("?"); + queryParams.add(directionList.get(i)); + queryParamTypes.add(Types.OTHER); + if (i < directionList.size() - 1) { + sb.append(","); + } + } + sb.append(")"); + } + + // Fill conditions on creation date + if (after.isPresent()) { + sb.append(" AND creation_time > ?"); + queryParams.add(after.get()); + queryParamTypes.add(Types.TIMESTAMP); + } + + sb.append(" ORDER BY creation_time DESC"); + + if (last.isPresent()) { + sb.append(" LIMIT ?"); + //sb.append(last.get().toString()); + queryParams.add(last.get()); + queryParamTypes.add(Types.INTEGER); + } + + String sql = sb.toString(); + + // Perform query + jdbcTemplate.query(sql, + (ps) -> { + for (int i = 0; i < queryParams.size(); i++) { + ps.setObject(i + 1, queryParams.get(i), + queryParamTypes.get(i)); + } + }, + (rs) -> { + sjdList.add(getShortJobDescriptionFromCurrentRow(rs)); + } + ); + + return jobs; + } + + private ShortJobDescription getShortJobDescriptionFromCurrentRow(ResultSet rs) + throws SQLException { + ShortJobDescription sjd = new ShortJobDescription(); + sjd.setId(rs.getString("job_id")); + sjd.setOwnerId(rs.getString("owner_id")); + sjd.setType(rs.getString("job_type")); + sjd.setPhase(ExecutionPhase.fromValue(rs.getString("phase"))); + sjd.setCreationTime( + toXMLGregorianCalendar(rs.getTimestamp("creation_time"))); + + return sjd; + } + private Object getJobPayload(String jobType, String json) { try { // TODO: switch on jobType @@ -145,4 +255,27 @@ public class JobDAO { throw new RuntimeException(ex); } } + + public static XMLGregorianCalendar toXMLGregorianCalendar(Timestamp t) + { + XMLGregorianCalendar cal = null; + try{ + cal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + + LocalDateTime ldt = t.toLocalDateTime(); + + cal.setYear(ldt.getYear()); + cal.setMonth(ldt.getMonthValue()); + cal.setDay(ldt.getDayOfMonth()); + cal.setHour(ldt.getHour()); + cal.setMinute(ldt.getMinute()); + cal.setSecond(ldt.getSecond()); + cal.setFractionalSecond(new BigDecimal("0." + ldt.getNano())); + + } catch(Exception e) { + e.printStackTrace(); + } + + return cal; + } } diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java index 5b2e92b57852dbeb31a29d6dda1c3c32a5bdc51b..2645cb36e98f8d17ce2c5f2b12cfc794755d0789 100644 --- a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java +++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java @@ -6,9 +6,13 @@ import it.inaf.oats.vospace.persistence.JobDAO; import it.inaf.oats.vospace.persistence.NodeDAO; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.Optional; import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.JobSummary; +import net.ivoa.xml.uws.v1.Jobs; +import net.ivoa.xml.uws.v1.ShortJobDescription; import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Property; @@ -36,6 +40,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder 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; +import java.util.List; @SpringBootTest @AutoConfigureMockMvc @@ -191,6 +196,51 @@ public class TransferControllerTest { verify(jobDao, times(1)).getJob(eq("123")); } + + @Test + public void testGetJobs() throws Exception { + + when(jobDao.getJobs(eq("user1"), any(), any(), any(), any())) + .thenReturn(this.getFakeJobs()); + + mockMvc.perform(get("/transfers") + .header("Authorization", "Bearer user1_token") + .param("LAST", "-3") + .accept(MediaType.APPLICATION_XML)) + .andDo(print()) + .andExpect(status().is4xxClientError()); + + + String xml2 = mockMvc.perform(get("/transfers") + .header("Authorization", "Bearer user1_token") + .accept(MediaType.APPLICATION_XML)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + } + + private Jobs getFakeJobs() { + Jobs jobs = new Jobs(); + jobs.setVersion("1.1"); + List<ShortJobDescription> sjdList = jobs.getJobref(); + sjdList.add(getFakeSJD1()); + + return jobs; + } + + private ShortJobDescription getFakeSJD1() { + ShortJobDescription sjd = new ShortJobDescription(); + sjd.setId("pippo1"); + sjd.setPhase(ExecutionPhase.QUEUED); + sjd.setOwnerId("user1"); + sjd.setType(JobService.JobType.pullFromVoSpace.toString()); + + LocalDateTime now = LocalDateTime.now(); + Timestamp ts = Timestamp.valueOf(now); + sjd.setCreationTime(JobDAO.toXMLGregorianCalendar(ts)); + + return sjd; + } private JobSummary getFakePendingJob() { JobSummary job = new JobSummary(); @@ -212,7 +262,7 @@ public class TransferControllerTest { } protected static String getResourceFileContent(String fileName) throws Exception { - try ( InputStream in = TransferControllerTest.class.getClassLoader().getResourceAsStream(fileName)) { + try (InputStream in = TransferControllerTest.class.getClassLoader().getResourceAsStream(fileName)) { return new String(in.readAllBytes(), StandardCharsets.UTF_8); } } diff --git a/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java index 4134c206bde4c8658e6dc21fb36e3619bb38a5ea..0173ba286f830a175b272f1aced105abc616599f 100644 --- a/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java +++ b/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java @@ -1,9 +1,11 @@ package it.inaf.oats.vospace.persistence; +import it.inaf.oats.vospace.JobService; +import java.util.List; import javax.sql.DataSource; 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.uws.v1.ShortJobDescription; import net.ivoa.xml.vospace.v2.Transfer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -14,6 +16,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.util.Optional; +import java.time.LocalDateTime; +import java.time.Month; +import net.ivoa.xml.uws.v1.Jobs; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {DataSourceConfig.class}) @@ -54,4 +60,119 @@ public class JobDAOTest { assertEquals(ExecutionPhase.EXECUTING, dao.getJob("123").get().getPhase()); } + + @Test + public void testJobsList() { + // Check no arguments + String user = "user1"; + List<ExecutionPhase> phaseList = List.of(); + List<JobService.JobType> directionList = List.of(); + Optional<LocalDateTime> after = Optional.ofNullable(null); + Optional<Integer> last = Optional.ofNullable(null); + + Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last); + + assertTrue(jobs != null); + List<ShortJobDescription> sjdList = jobs.getJobref(); + assertTrue(sjdList != null); + assertTrue(!sjdList.isEmpty()); + assertTrue( + sjdList.stream().noneMatch( + (i) -> { + return i.getPhase().equals(ExecutionPhase.ARCHIVED); + } + ) + ); + assertEquals(3, sjdList.size()); + } + + @Test + public void testJobsListLimit() { + // Check no arguments + String user = "user1"; + List<ExecutionPhase> phaseList = List.of(); + List<JobService.JobType> directionList = List.of(); + Optional<LocalDateTime> after = Optional.ofNullable(null); + Optional<Integer> last = Optional.of(2); + + Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last); + List<ShortJobDescription> sjdList = jobs.getJobref(); + assertEquals(2, sjdList.size()); + + } + + @Test + public void testJobsPhase() { + // Check no arguments + String user = "user1"; + List<ExecutionPhase> phaseList + = List.of(ExecutionPhase.PENDING, ExecutionPhase.EXECUTING); + List<JobService.JobType> directionList = List.of(); + Optional<LocalDateTime> after = Optional.ofNullable(null); + Optional<Integer> last = Optional.ofNullable(null); + + Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last); + List<ShortJobDescription> sjdList = jobs.getJobref(); + assertEquals(sjdList.size(), 2); + assertEquals("pippo5", sjdList.get(0).getId()); + assertEquals("pippo2", sjdList.get(1).getId()); + } + + @Test + public void testJobsDirection() { + // Check no arguments + String user = "user1"; + List<ExecutionPhase> phaseList = List.of(); + List<JobService.JobType> directionList + = List.of(JobService.JobType.pullFromVoSpace, + JobService.JobType.pullToVoSpace); + + Optional<LocalDateTime> after = Optional.ofNullable(null); + Optional<Integer> last = Optional.ofNullable(null); + Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last); + List<ShortJobDescription> sjdList = jobs.getJobref(); + assertEquals(2, sjdList.size()); + assertEquals("pippo3", sjdList.get(0).getId()); + assertEquals("pippo2", sjdList.get(1).getId()); + } + + @Test + public void testJobsAfter() { + // Check no arguments + String user = "user1"; + List<ExecutionPhase> phaseList = List.of(); + List<JobService.JobType> directionList = List.of(); + + LocalDateTime ldt + = LocalDateTime.of(2013, Month.FEBRUARY, 7, 18, 15); + Optional<LocalDateTime> after = Optional.of(ldt); + + Optional<Integer> last = Optional.ofNullable(null); + Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last); + List<ShortJobDescription> sjdList = jobs.getJobref(); + assertEquals(2, sjdList.size()); + assertEquals("pippo5", sjdList.get(0).getId()); + assertEquals("pippo3", sjdList.get(1).getId()); + } + + @Test + public void testJobsAllchecks() { + // Check no arguments + String user = "user1"; + List<ExecutionPhase> phaseList = List.of(ExecutionPhase.QUEUED, + ExecutionPhase.PENDING); + List<JobService.JobType> directionList = + List.of(JobService.JobType.pullFromVoSpace, + JobService.JobType.pullToVoSpace); + + LocalDateTime ldt + = LocalDateTime.of(2013, Month.FEBRUARY, 7, 18, 15); + Optional<LocalDateTime> after = Optional.of(ldt); + + Optional<Integer> last = Optional.of(2); + Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last); + List<ShortJobDescription> sjdList = jobs.getJobref(); + assertEquals(1, sjdList.size()); + assertEquals("pippo3", sjdList.get(0).getId()); + } } diff --git a/src/test/resources/test-data.sql b/src/test/resources/test-data.sql index 2a0c10bc91be31372de426fc50c14c550911a448..67aa878e0f8597cc445c87f9eb1fe332ad47ae54 100644 --- a/src/test/resources/test-data.sql +++ b/src/test/resources/test-data.sql @@ -11,3 +11,12 @@ INSERT INTO node (parent_path, parent_relative_path, name, type, owner_id, creat INSERT INTO node (parent_path, parent_relative_path, name, type, owner_id, creator_id, is_public) VALUES ('', NULL, 'test2', 'container', 'user2', 'user2', true); -- /test2 INSERT INTO node (parent_path, parent_relative_path, name, type, owner_id, creator_id, is_public) VALUES ('5', '', 'f4', 'container', 'user2', 'user2', true); -- /test2/f4 (rel: /f4) INSERT INTO node (parent_path, parent_relative_path, name, type, owner_id, creator_id, is_public) VALUES ('5', '', 'f5', 'container', 'user2', 'user2', true); -- /test2/f5 (rel: /f5) + +DELETE FROM job; + +INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo1', 'user1', 'pullFromVoSpace', 'ARCHIVED', NULL, NULL, '2011-06-22 19:10:25', NULL, NULL); +INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo2', 'user1', 'pullToVoSpace', 'PENDING', NULL, NULL, '2012-06-22 19:10:25', NULL, NULL); +INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo3', 'user1', 'pullFromVoSpace', 'QUEUED', NULL, NULL, '2013-06-22 19:10:25', NULL, NULL); +INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo4', 'user2', 'copyNode', 'PENDING', NULL, NULL, '2014-06-22 19:10:25', NULL, NULL); +INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo5', 'user1', 'pushToVoSpace', 'EXECUTING', NULL, NULL, '2015-06-22 19:10:25', NULL, NULL); +INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo6', 'user2', 'pullFromVoSpace', 'PENDING', NULL, NULL, '2015-06-22 19:10:25', NULL, NULL); \ No newline at end of file