diff --git a/pom.xml b/pom.xml index 994cdaaa6de2e8e80853e11e61ac8543a30555fe..59a899333b52ff54c7a50344e2ca10c0e640dc31 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,12 @@ <artifactId>vospace-datamodel</artifactId> <version>1.0-SNAPSHOT</version> </dependency> + + <dependency> + <groupId>org.kamranzafar</groupId> + <artifactId>jtar</artifactId> + <version>2.3</version> + </dependency> </dependencies> <profiles> diff --git a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java new file mode 100644 index 0000000000000000000000000000000000000000..7adc30cd3d4d1b4382f7b787df12b5d75a4a23cb --- /dev/null +++ b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java @@ -0,0 +1,47 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.inaf.ia2.transfer.controller; + +import it.inaf.ia2.transfer.auth.TokenPrincipal; +import it.inaf.ia2.transfer.service.ArchiveJob; +import it.inaf.ia2.transfer.service.ArchiveService; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +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 ArchiveFileController { + + @Autowired + private ArchiveService archiveService; + + @Autowired + private HttpServletRequest request; + + @PostMapping(value = "/tar", consumes = MediaType.APPLICATION_JSON_VALUE) + public void createTarArchive(@RequestParam(value = "jobId", required = true) String jobId, @RequestBody List<String> vosPaths) { + + ArchiveJob job = new ArchiveJob(); + job.setPrincipal((TokenPrincipal) request.getUserPrincipal()); + job.setJobId(jobId); + job.setType(ArchiveJob.Type.TAR); + job.setVosPaths(vosPaths); + + startArchiveJob(job); + } + + private void startArchiveJob(ArchiveJob job) { + CompletableFuture.runAsync(() -> { + archiveService.createArchive(job); + }); + } +} diff --git a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..91cef3448b7dcc30ac5dd4f049a936232f5cc63c --- /dev/null +++ b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java @@ -0,0 +1,16 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package it.inaf.ia2.transfer.controller; + +import java.util.List; + +public class ArchiveRequest { + + private List<String> vosPaths; + String jobId; + String type; +} diff --git a/src/main/java/it/inaf/ia2/transfer/controller/GetFileController.java b/src/main/java/it/inaf/ia2/transfer/controller/GetFileController.java index cab9e3a0441d4b54ec772dde1b9f94c87f103cd9..be4e4fe28d8f1adcd206f386bf6adaa72a10abf1 100644 --- a/src/main/java/it/inaf/ia2/transfer/controller/GetFileController.java +++ b/src/main/java/it/inaf/ia2/transfer/controller/GetFileController.java @@ -5,9 +5,10 @@ */ package it.inaf.ia2.transfer.controller; -import it.inaf.ia2.transfer.auth.GmsClient; +import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.auth.TokenPrincipal; import it.inaf.ia2.transfer.persistence.FileDAO; +import it.inaf.ia2.transfer.service.AuthorizationService; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -37,7 +38,7 @@ public class GetFileController extends FileController { private FileDAO fileDAO; @Autowired - private GmsClient gmsClient; + private AuthorizationService authorizationService; @Autowired private HttpServletResponse response; @@ -46,16 +47,16 @@ public class GetFileController extends FileController { public ResponseEntity<?> getFile() { String path = getPath(); - + LOG.debug("getFile called for path {}", path); - + Optional<FileInfo> optFileInfo = fileDAO.getFileInfo(path); if (optFileInfo.isPresent()) { FileInfo fileInfo = optFileInfo.get(); - if (!fileInfo.isIsPublic() && !privateButDownloadable(fileInfo)) { + if (!authorizationService.isDownloadable(fileInfo, (TokenPrincipal) request.getUserPrincipal())) { return new ResponseEntity<>("Unauthorized", UNAUTHORIZED); } @@ -65,31 +66,6 @@ public class GetFileController extends FileController { } } - private boolean privateButDownloadable(FileInfo fileInfo) { - - TokenPrincipal principal = (TokenPrincipal) request.getUserPrincipal(); - - String token = principal.getToken(); - if (token == null) { - return false; - } - - if (principal.getName().equals(fileInfo.getOwnerId())) { - return true; - } - - // TODO: configure cache - if (fileInfo.getGroupRead() == null) { - return false; - } - for (String group : fileInfo.getGroupRead()) { - if (gmsClient.isMemberOf(token, group)) { - return true; - } - } - return false; - } - private ResponseEntity<?> getFileResponse(FileInfo fileInfo) { File file = new File(fileInfo.getOsPath()); @@ -105,7 +81,7 @@ public class GetFileController extends FileController { } String vosName = fileInfo.getVirtualPath().substring(fileInfo.getVirtualPath().lastIndexOf("/") + 1); - + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(vosName, StandardCharsets.UTF_8)); response.setHeader("Content-Length", String.valueOf(file.length())); response.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/it/inaf/ia2/transfer/controller/PutFileController.java b/src/main/java/it/inaf/ia2/transfer/controller/PutFileController.java index e9c8e7f4e60cc5e1c7ec94cbeabe07be52b17203..5bdbae00166c01bccd1ddaa66234b51fd76599da 100644 --- a/src/main/java/it/inaf/ia2/transfer/controller/PutFileController.java +++ b/src/main/java/it/inaf/ia2/transfer/controller/PutFileController.java @@ -5,6 +5,7 @@ */ package it.inaf.ia2.transfer.controller; +import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.JobDAO; import java.io.File; diff --git a/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java b/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java index ce9c335de2fb5c397ad2c78cd41dd9d753101e7e..6a22024875a87de8a5c984f68993096162d6429e 100644 --- a/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java +++ b/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java @@ -5,7 +5,7 @@ */ package it.inaf.ia2.transfer.persistence; -import it.inaf.ia2.transfer.controller.FileInfo; +import it.inaf.ia2.transfer.persistence.model.FileInfo; import java.nio.file.Path; import java.sql.Array; import java.sql.PreparedStatement; @@ -14,6 +14,7 @@ import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import javax.sql.DataSource; @@ -41,7 +42,7 @@ public class FileDAO { + "content_type, content_encoding, content_length, content_md5,\n" + "accept_views, provide_views, l.location_type, n.path <> n.relative_path AS virtual_parent,\n" + "(SELECT user_name FROM users WHERE user_id = creator_id) AS username,\n" - + "base_path, get_os_path(n.node_id) AS os_path\n" + + "base_path, get_os_path(n.node_id) AS os_path, ? AS vos_path\n" + "FROM node n\n" + "JOIN location l ON (n.location_id IS NOT NULL AND n.location_id = l.location_id) OR (n.location_id IS NULL AND l.location_id = ?)\n" + "LEFT JOIN storage s ON s.storage_id = l.storage_dest_id\n" @@ -49,30 +50,13 @@ public class FileDAO { FileInfo fileInfo = jdbcTemplate.query(conn -> { PreparedStatement ps = conn.prepareStatement(sql); - ps.setInt(1, uploadLocationId); - ps.setString(2, virtualPath); + ps.setString(1, virtualPath); + ps.setInt(2, uploadLocationId); + ps.setString(3, virtualPath); return ps; }, rs -> { if (rs.next()) { - FileInfo fi = new FileInfo(); - fi.setNodeId(rs.getInt("node_id")); - fi.setIsPublic(rs.getBoolean("is_public")); - fi.setGroupRead(toList(rs.getArray("group_read"))); - fi.setGroupWrite(toList(rs.getArray("group_write"))); - fi.setOwnerId(rs.getString("creator_id")); - fi.setAsyncTrans(rs.getBoolean("async_trans")); - fi.setAcceptViews(toList(rs.getArray("accept_views"))); - fi.setProvideViews(toList(rs.getArray("provide_views"))); - fi.setVirtualParent(rs.getBoolean("virtual_parent")); - fi.setVirtualPath(virtualPath); - fi.setContentEncoding(rs.getString("content_encoding")); - fi.setContentLength(rs.getLong("content_length")); - fi.setContentMd5(rs.getString("content_md5")); - fi.setContentType(rs.getString("content_type")); - - fillOsPath(fi, rs); - - return fi; + return getFileInfo(rs); } return null; }); @@ -80,38 +64,6 @@ public class FileDAO { return Optional.ofNullable(fileInfo); } - private void fillOsPath(FileInfo fi, ResultSet rs) throws SQLException { - - if (fi.getProvideViews() != null) { - for (String provideView : fi.getProvideViews()) { - if ("urn:list-of-files".equals(provideView)) { - // Not a physical file - return; - } - } - } - - String basePath = rs.getString("base_path"); - String osPath = rs.getString("os_path"); - if (osPath.startsWith("/")) { - osPath = osPath.substring(1); - } - - Path completeOsPath = Path.of(basePath); - - boolean asyncLocation = "async".equals(rs.getString("location_type")); - - if (asyncLocation) { - String username = rs.getString("username"); - completeOsPath = completeOsPath.resolve(username).resolve("retrieve"); - } else if (fi.hasVirtualParent()) { - completeOsPath = completeOsPath.resolve(fi.getOwnerId()); - } - - completeOsPath = completeOsPath.resolve(osPath); - fi.setOsPath(completeOsPath.toString()); - } - private List<String> toList(Array array) throws SQLException { if (array == null) { return new ArrayList<>(); @@ -168,4 +120,84 @@ public class FileDAO { }); } + + // TODO: set maximum list size or use stream to avoid memory issues + public List<FileInfo> getArchiveFileInfos(List<String> vosPaths) { + + if (vosPaths.isEmpty()) { + throw new IllegalArgumentException("Received empty list of paths"); + } + + String sql = "SELECT n.node_id, is_public, group_read, group_write, creator_id, async_trans,\n" + + "content_type, content_encoding, content_length, content_md5,\n" + + "accept_views, provide_views, l.location_type, n.path <> n.relative_path AS virtual_parent,\n" + + "(SELECT user_name FROM users WHERE user_id = creator_id) AS username,\n" + + "base_path, get_os_path(n.node_id) AS os_path, get_vos_path(n.node_id) AS vos_path\n" + + "FROM node n\n" + + "JOIN location l ON (n.location_id IS NOT NULL AND n.location_id = l.location_id) OR (n.location_id IS NULL AND l.location_id = ?)\n" + + "LEFT JOIN storage s ON s.storage_id = l.storage_dest_id\n" + + "WHERE " + String.join(" OR ", Collections.nCopies(vosPaths.size(), "n.node_id = id_from_vos_path(?)")) + + "\nORDER BY vos_path ASC"; + + return jdbcTemplate.query(conn -> { + PreparedStatement ps = conn.prepareStatement(sql); + int i = 0; + ps.setInt(++i, uploadLocationId); + for (String vosPath : vosPaths) { + ps.setString(++i, vosPath); + } + return ps; + }, rs -> { + List<FileInfo> fileInfos = new ArrayList<>(); + while (rs.next()) { + fileInfos.add(getFileInfo(rs)); + } + return fileInfos; + }); + } + + private FileInfo getFileInfo(ResultSet rs) throws SQLException { + FileInfo fi = new FileInfo(); + fi.setNodeId(rs.getInt("node_id")); + fi.setPublic(rs.getBoolean("is_public")); + fi.setGroupRead(toList(rs.getArray("group_read"))); + fi.setGroupWrite(toList(rs.getArray("group_write"))); + fi.setOwnerId(rs.getString("creator_id")); + fi.setAsyncTrans(rs.getBoolean("async_trans")); + fi.setAcceptViews(toList(rs.getArray("accept_views"))); + fi.setProvideViews(toList(rs.getArray("provide_views"))); + fi.setVirtualParent(rs.getBoolean("virtual_parent")); + fi.setVirtualPath(rs.getString("vos_path")); + fi.setContentEncoding(rs.getString("content_encoding")); + fi.setContentLength(rs.getLong("content_length")); + fi.setContentMd5(rs.getString("content_md5")); + fi.setContentType(rs.getString("content_type")); + + fillOsPath(fi, rs); + + return fi; + } + + private void fillOsPath(FileInfo fi, ResultSet rs) throws SQLException { + + String basePath = rs.getString("base_path"); + String osPath = rs.getString("os_path"); + if (osPath.startsWith("/")) { + osPath = osPath.substring(1); + } + + Path completeOsPath = Path.of(basePath); + + boolean asyncLocation = "async".equals(rs.getString("location_type")); + + if (asyncLocation) { + String username = rs.getString("username"); + completeOsPath = completeOsPath.resolve(username).resolve("retrieve"); + } else if (fi.hasVirtualParent()) { + completeOsPath = completeOsPath.resolve(fi.getOwnerId()); + } + + completeOsPath = completeOsPath.resolve(osPath); + fi.setOsPath(completeOsPath.toString()); + } } diff --git a/src/main/java/it/inaf/ia2/transfer/controller/FileInfo.java b/src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java similarity index 96% rename from src/main/java/it/inaf/ia2/transfer/controller/FileInfo.java rename to src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java index 9a0c717f78eec7b39060c52a87b2e5841d3181be..3fb8d09612ec34c8bc6f4140b8d1d1eb74cec3b5 100644 --- a/src/main/java/it/inaf/ia2/transfer/controller/FileInfo.java +++ b/src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java @@ -3,7 +3,7 @@ * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ -package it.inaf.ia2.transfer.controller; +package it.inaf.ia2.transfer.persistence.model; import java.util.List; @@ -81,11 +81,11 @@ public class FileInfo { this.virtualPath = virtualPath; } - public boolean isIsPublic() { + public boolean isPublic() { return isPublic; } - public void setIsPublic(boolean isPublic) { + public void setPublic(boolean isPublic) { this.isPublic = isPublic; } diff --git a/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java b/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java new file mode 100644 index 0000000000000000000000000000000000000000..0ae2c083e242723d11b7369455b3927d6fda546d --- /dev/null +++ b/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java @@ -0,0 +1,54 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.inaf.ia2.transfer.service; + +import it.inaf.ia2.transfer.auth.TokenPrincipal; +import java.util.List; + +public class ArchiveJob { + + public static enum Type { + TAR, + ZIP + } + + private List<String> vosPaths; + private TokenPrincipal tokenPrincipal; + private String jobId; + private Type type; + + public List<String> getVosPaths() { + return vosPaths; + } + + public void setVosPaths(List<String> vosPaths) { + this.vosPaths = vosPaths; + } + + public TokenPrincipal getPrincipal() { + return tokenPrincipal; + } + + public void setPrincipal(TokenPrincipal tokenPrincipal) { + this.tokenPrincipal = tokenPrincipal; + } + + public String getJobId() { + return jobId; + } + + public void setJobId(String jobId) { + this.jobId = jobId; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } +} diff --git a/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java b/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java new file mode 100644 index 0000000000000000000000000000000000000000..8ff8c96a78cea2e23681ed160db4d294ade32f42 --- /dev/null +++ b/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java @@ -0,0 +1,109 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.inaf.ia2.transfer.service; + +import it.inaf.ia2.transfer.persistence.FileDAO; +import it.inaf.ia2.transfer.persistence.model.FileInfo; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.kamranzafar.jtar.TarEntry; +import org.kamranzafar.jtar.TarOutputStream; +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.stereotype.Service; + +@Service +public class ArchiveService { + + private static final Logger LOG = LoggerFactory.getLogger(ArchiveService.class); + + private final static int BUFFER_SIZE = 100 * 1024; + + @Autowired + private FileDAO fileDAO; + + @Autowired + private AuthorizationService authorizationService; + + private final File generatedDir; + + public ArchiveService(@Value("${generated.dir}") String generatedDir) { + this.generatedDir = new File(generatedDir); + if (!this.generatedDir.exists()) { + if (!this.generatedDir.mkdirs()) { + throw new IllegalStateException("Unable to create directory " + this.generatedDir.getAbsolutePath()); + } + } + } + + public void createArchive(ArchiveJob job) { + + LOG.trace("Started archive job " + job.getJobId()); + + try { + // TODO: check total size limit + // TODO: switch on archive type + File parentDir = generatedDir.toPath().resolve(job.getPrincipal().getName()).toFile(); + + if (!parentDir.exists()) { + if (!parentDir.mkdirs()) { + throw new IllegalStateException("Unable to create directory " + parentDir.getAbsolutePath()); + } + } + + File archiveFile = parentDir.toPath().resolve(job.getJobId() + ".tar").toFile(); + if (!archiveFile.createNewFile()) { + throw new IllegalStateException("Unable to create file " + archiveFile.getAbsolutePath()); + } + + try ( TarOutputStream tos = new TarOutputStream( + new BufferedOutputStream(new FileOutputStream(archiveFile)))) { + + for (FileInfo fileInfo : fileDAO.getArchiveFileInfos(job.getVosPaths())) { + // TODO: handle different locations + + if (!authorizationService.isDownloadable(fileInfo, job.getPrincipal())) { + // TODO: proper exception type + throw new RuntimeException("Unauthorized"); + } + + File file = new File(fileInfo.getOsPath()); + LOG.trace("Adding file " + file.getAbsolutePath() + " to tar archive"); + writeFileIntoTarArchive(file, tos); + } + } + // TODO: update job status + + } catch (Throwable t) { + LOG.error("Error happened creating archive", t); + } + } + + private void writeFileIntoTarArchive(File file, TarOutputStream tos) throws IOException { + TarEntry tarEntry = new TarEntry(file, file.getName()); + + try ( InputStream is = new FileInputStream(file)) { + tos.putNextEntry(tarEntry); + try ( BufferedInputStream origin = new BufferedInputStream(is)) { + int count; + byte data[] = new byte[BUFFER_SIZE]; + + while ((count = origin.read(data)) != -1) { + tos.write(data, 0, count); + } + + tos.flush(); + } + } + } +} diff --git a/src/main/java/it/inaf/ia2/transfer/service/AuthorizationService.java b/src/main/java/it/inaf/ia2/transfer/service/AuthorizationService.java new file mode 100644 index 0000000000000000000000000000000000000000..7e40fc50643171410d1fdf529d711524bc6c941c --- /dev/null +++ b/src/main/java/it/inaf/ia2/transfer/service/AuthorizationService.java @@ -0,0 +1,46 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.inaf.ia2.transfer.service; + +import it.inaf.ia2.transfer.auth.GmsClient; +import it.inaf.ia2.transfer.auth.TokenPrincipal; +import it.inaf.ia2.transfer.persistence.model.FileInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class AuthorizationService { + + @Autowired + private GmsClient gmsClient; + + public boolean isDownloadable(FileInfo fileInfo, TokenPrincipal principal) { + + if (fileInfo.isPublic()) { + return true; + } + + String token = principal.getToken(); + if (token == null) { + return false; + } + + if (principal.getName().equals(fileInfo.getOwnerId())) { + return true; + } + + // TODO: configure cache + if (fileInfo.getGroupRead() == null) { + return false; + } + for (String group : fileInfo.getGroupRead()) { + if (gmsClient.isMemberOf(token, group)) { + return true; + } + } + return false; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5379521217b6dbf94d5491f3e0575b08403aa041..1dca7a0293234ff3aa3b8092a6b1270e78b0568d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,8 +5,7 @@ file-catalog.datasource.jdbc-url=jdbc:postgresql://127.0.0.1:5432/vospace_testdb file-catalog.datasource.username=postgres file-catalog.datasource.password= -gms_base_url=http://localhost:8082/gms -jwks_uri=http://localhost/rap-ia2/auth/oidc/jwks +generated.dir=/tmp/vospace/gen upload_location_id=3 diff --git a/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java b/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..08477b992a84d346b278f487b07d56059415a014 --- /dev/null +++ b/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java @@ -0,0 +1,52 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.inaf.ia2.transfer.controller; + +import it.inaf.ia2.transfer.service.ArchiveJob; +import it.inaf.ia2.transfer.service.ArchiveService; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +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.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ArchiveFileControllerTest { + + @MockBean + private ArchiveService archiveService; + + @Autowired + private MockMvc mockMvc; + + @Test + public void testCreateTarArchive() throws Exception { + + mockMvc.perform(post("/tar?jobId=123") + .contentType(MediaType.APPLICATION_JSON) + .content("[\"/path/to/file1\", \"/path/to/file2\"]")) + .andDo(print()) + .andExpect(status().isOk()); + + verify(archiveService, times(1)).createArchive(argThat(job -> { + assertEquals("123", job.getJobId()); + assertEquals(ArchiveJob.Type.TAR, job.getType()); + assertEquals("anonymous", job.getPrincipal().getName()); + assertEquals(2, job.getVosPaths().size()); + return true; + })); + } +} diff --git a/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java b/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java index d90d0f3a0865f472278cbe9d61b4b6b970cf937d..d67b07a17a775feecf7a138f0a4dd5246000e6c4 100644 --- a/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java +++ b/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java @@ -5,6 +5,7 @@ */ package it.inaf.ia2.transfer.controller; +import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.aa.jwt.TokenParser; import it.inaf.ia2.transfer.auth.GmsClient; import it.inaf.ia2.transfer.persistence.FileDAO; @@ -64,7 +65,7 @@ public class GetFileControllerTest { FileInfo fileInfo = new FileInfo(); fileInfo.setOsPath(tempFile.getAbsolutePath()); fileInfo.setVirtualPath("/path/to/myfile"); - fileInfo.setIsPublic(true); + fileInfo.setPublic(true); when(fileDao.getFileInfo(eq("/path/to/myfile"))).thenReturn(Optional.of(fileInfo)); @@ -79,7 +80,7 @@ public class GetFileControllerTest { FileInfo fileInfo = new FileInfo(); fileInfo.setOsPath(tempFile.getAbsolutePath()); fileInfo.setVirtualPath("/path/to/myfile"); - fileInfo.setIsPublic(true); + fileInfo.setPublic(true); when(fileDao.getFileInfo(eq("/path/to/myfile"))).thenReturn(Optional.of(fileInfo)); @@ -100,7 +101,7 @@ public class GetFileControllerTest { FileInfo fileInfo = new FileInfo(); fileInfo.setOsPath("/this/doesnt/exists"); - fileInfo.setIsPublic(true); + fileInfo.setPublic(true); when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo)); diff --git a/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java b/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java index 33909c9f9708ef30d2dff3b918e6aab1c86dde85..e2b84b1f358b323fe2cd94727904fdd146327a43 100644 --- a/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java +++ b/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java @@ -5,6 +5,7 @@ */ package it.inaf.ia2.transfer.controller; +import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.JobDAO; import java.io.File; @@ -174,7 +175,7 @@ public class PutFileControllerTest { private FileInfo createBaseFileInfo(String fileName) { FileInfo fileInfo = new FileInfo(); fileInfo.setOsPath(getTestFilePath(fileName)); - fileInfo.setIsPublic(false); + fileInfo.setPublic(false); when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo)); diff --git a/src/test/java/it/inaf/ia2/transfer/persistence/FileDAOTest.java b/src/test/java/it/inaf/ia2/transfer/persistence/FileDAOTest.java index 77a23d82b377bcf9a0eb34e1ebb2a6ebc2130b61..76e69f38842316c6fabb676fb5e93d8fb13c9f8f 100644 --- a/src/test/java/it/inaf/ia2/transfer/persistence/FileDAOTest.java +++ b/src/test/java/it/inaf/ia2/transfer/persistence/FileDAOTest.java @@ -5,7 +5,7 @@ */ package it.inaf.ia2.transfer.persistence; -import it.inaf.ia2.transfer.controller.FileInfo; +import it.inaf.ia2.transfer.persistence.model.FileInfo; import java.util.Optional; import javax.sql.DataSource; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java b/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..09aadff229459d8b3b019dedaed7b72245232d1f --- /dev/null +++ b/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java @@ -0,0 +1,107 @@ +/* + * This file is part of vospace-file-service + * Copyright (C) 2021 Istituto Nazionale di Astrofisica + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.inaf.ia2.transfer.service; + +import it.inaf.ia2.transfer.auth.TokenPrincipal; +import it.inaf.ia2.transfer.persistence.FileDAO; +import it.inaf.ia2.transfer.persistence.model.FileInfo; +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.when; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.FileSystemUtils; + +@ExtendWith(MockitoExtension.class) +public class ArchiveServiceTest { + + @Mock + private FileDAO fileDAO; + + @Mock + private AuthorizationService authorizationService; + + private ArchiveService archiveService; + + private static File tmpDir; + + @BeforeAll + public static void setUpClass() throws Exception { + tmpDir = Files.createTempDirectory("generated").toFile(); + } + + @AfterAll + public static void tearDownClass() throws Exception { + FileSystemUtils.deleteRecursively(tmpDir); + } + + @BeforeEach + public void setUp() { + archiveService = new ArchiveService(tmpDir.getAbsolutePath()); + ReflectionTestUtils.setField(archiveService, "fileDAO", fileDAO); + ReflectionTestUtils.setField(archiveService, "authorizationService", authorizationService); + } + + @Test + public void testTarGeneration() throws Exception { + + File tmpParent = tmpDir.toPath().resolve("test1").toFile(); + File file1 = createFile(tmpParent, "dir1/a/b/file1"); + File file2 = createFile(tmpParent, "dir1/a/b/file2"); + File file3 = createFile(tmpParent, "dir2/c/file3"); + File file4 = createFile(tmpParent, "dir2/c/file4"); + File file5 = createFile(tmpParent, "dir2/c/d/file5"); + File file6 = createFile(tmpParent, "file6"); + + ArchiveJob job = new ArchiveJob(); + job.setPrincipal(new TokenPrincipal("user123", "token123")); + job.setJobId("abcdef"); + job.setType(ArchiveJob.Type.TAR); + job.setVosPaths(Arrays.asList("/path/to/file6"));//"/path/to/dir1", "/path/to/dir2", + + when(authorizationService.isDownloadable(any(), any())).thenReturn(true); + + List<FileInfo> fileInfos = new ArrayList<>(); + FileInfo fileInfo = new FileInfo(); + fileInfo.setOsPath(file6.getAbsolutePath()); + fileInfos.add(fileInfo); + + when(fileDAO.getArchiveFileInfos(any())).thenReturn(fileInfos); + + archiveService.createArchive(job); + + File result = tmpDir.toPath().resolve("user123").resolve("abcdef.tar").toFile(); + assertTrue(result.exists()); + } + + private File createFile(File parent, String path) throws Exception { + parent.mkdir(); + String[] files = path.split("/"); + for (int i = 0; i < files.length; i++) { + File file = parent.toPath().resolve(files[i]).toFile(); + if (i == files.length - 1) { + file.createNewFile(); + Files.write(file.toPath(), "some data".getBytes()); + return file; + } else { + file.mkdir(); + parent = file; + } + } + throw new IllegalStateException("Files have to be created"); + } +}