Loading src/main/java/it/inaf/ia2/transfer/controller/PutFileController.java +26 −8 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ package it.inaf.ia2.transfer.controller; import it.inaf.ia2.transfer.exception.FileNotFoundException; import it.inaf.ia2.transfer.exception.InsufficientStorageException; import it.inaf.ia2.transfer.exception.InvalidArgumentException; import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.FileDAO; Loading Loading @@ -64,14 +65,24 @@ public class PutFileController extends FileController { Optional<FileInfo> optFileInfo = fileDAO.getFileInfo(path); if (optFileInfo.isPresent()) { try (InputStream in = file != null ? file.getInputStream() : request.getInputStream()) { FileInfo fileInfo = optFileInfo.get(); String parentPath = fileInfo.getVirtualPath().substring(0, fileInfo.getVirtualPath().lastIndexOf("/")); Long remainingQuota = fileDAO.getRemainingQuota(parentPath); // if MultipartFile provides file size it is possible to check // quota limit before reading the stream if (remainingQuota != null && file != null && file.getSize() > remainingQuota) { throw new InsufficientStorageException(fileInfo.getVirtualPath()); } if (file != null) { fileInfo.setContentType(file.getContentType()); } fileInfo.setContentEncoding(contentEncoding); storeGenericFile(fileInfo, in, jobId); try (InputStream in = file != null ? file.getInputStream() : request.getInputStream()) { storeGenericFile(fileInfo, in, jobId, remainingQuota); } catch (IOException | NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } Loading @@ -81,7 +92,7 @@ public class PutFileController extends FileController { }, jobId); } private void storeGenericFile(FileInfo fileInfo, InputStream is, String jobId) throws IOException, NoSuchAlgorithmException { private void storeGenericFile(FileInfo fileInfo, InputStream is, String jobId, Long remainingQuota) throws IOException, NoSuchAlgorithmException { File file = new File(fileInfo.getOsPath()); Loading Loading @@ -112,6 +123,13 @@ public class PutFileController extends FileController { } Long fileSize = Files.size(file.toPath()); // Quota limit is checked again to handle cases where MultipartFile is not used if (remainingQuota != null && fileSize > remainingQuota) { file.delete(); throw new InsufficientStorageException(fileInfo.getVirtualPath()); } String md5Checksum = makeMD5Checksum(file); fileDAO.updateFileAttributes(fileInfo.getNodeId(), Loading src/main/java/it/inaf/ia2/transfer/exception/InsufficientStorageException.java 0 → 100644 +18 −0 Original line number Diff line number Diff line /* * 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.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.INSUFFICIENT_STORAGE) public class InsufficientStorageException extends JobException { public InsufficientStorageException(String path) { super(Type.FATAL, "Quota Exceeded"); setErrorDetail("QuotaExceeded Path: " + path); } } src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java +70 −4 Original line number Diff line number Diff line Loading @@ -5,14 +5,18 @@ */ package it.inaf.ia2.transfer.controller; import it.inaf.ia2.transfer.exception.InsufficientStorageException; 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.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Optional; import java.util.UUID; import javax.servlet.ServletInputStream; import net.ivoa.xml.uws.v1.ExecutionPhase; import org.assertj.core.util.Files; import org.junit.jupiter.api.AfterAll; Loading @@ -22,6 +26,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import org.mockito.Mockito; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; Loading Loading @@ -70,6 +76,8 @@ public class PutFileControllerTest { @Test public void putGenericFile() throws Exception { when(fileDao.getRemainingQuota(any())).thenReturn(null); String randomFileName = UUID.randomUUID().toString(); createBaseFileInfo(randomFileName); Loading Loading @@ -99,6 +107,8 @@ public class PutFileControllerTest { private void putGenericFileWithNameConflict(String name1, String name2, String name3) throws Exception { when(fileDao.getRemainingQuota(any())).thenReturn(null); createBaseFileInfo(name1); MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", "text/plain", "content".getBytes()); Loading Loading @@ -143,6 +153,8 @@ public class PutFileControllerTest { @Test public void putGenericFileWithJobId() throws Exception { when(fileDao.getRemainingQuota(any())).thenReturn(null); when(jobDAO.isJobExisting("pippo10")).thenReturn(false); when(jobDAO.isJobExisting("pippo5")).thenReturn(true); Loading Loading @@ -222,6 +234,59 @@ public class PutFileControllerTest { verify(jobDAO, times(1)).setJobError(eq("abcdef"), any()); } @Test public void testQuotaExceededMultipart() throws Exception { when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l); createBaseFileInfo(); MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", null, "content".getBytes()); Exception ex = mockMvc.perform(putMultipart("/path/to/test.txt") .file(fakeFile)) .andDo(print()) .andExpect(status().is5xxServerError()) .andReturn().getResolvedException(); verify(fileDao, times(1)).getRemainingQuota(eq("/path/to")); assertTrue(ex instanceof InsufficientStorageException); } @Test public void testQuotaExceededStream() throws Exception { when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l); createBaseFileInfo(); MockHttpServletRequestBuilder streamBuilder = put("/path/to/test.txt"); streamBuilder.with(new RequestPostProcessor() { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { MockHttpServletRequest spyRequest = spy(request); ByteArrayInputStream bais = new ByteArrayInputStream("some data".getBytes()); ServletInputStream sis = mock(ServletInputStream.class); try { when(sis.transferTo(any())).thenAnswer(i -> bais.transferTo(i.getArgument(0))); } catch (IOException ex) { } Mockito.doReturn(sis).when(spyRequest).getInputStream(); return spyRequest; } }); Exception ex = mockMvc.perform(streamBuilder) .andDo(print()) .andExpect(status().is5xxServerError()) .andReturn().getResolvedException(); verify(fileDao, times(1)).getRemainingQuota(eq("/path/to")); assertTrue(ex instanceof InsufficientStorageException); } private FileInfo createBaseFileInfo() { String randomFileName = UUID.randomUUID().toString(); return createBaseFileInfo(randomFileName); Loading @@ -230,6 +295,7 @@ public class PutFileControllerTest { private FileInfo createBaseFileInfo(String fileName) { FileInfo fileInfo = new FileInfo(); fileInfo.setOsPath(getTestFilePath(fileName)); fileInfo.setVirtualPath("/path/to/" + fileName); fileInfo.setPublic(false); when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo)); Loading Loading
src/main/java/it/inaf/ia2/transfer/controller/PutFileController.java +26 −8 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ package it.inaf.ia2.transfer.controller; import it.inaf.ia2.transfer.exception.FileNotFoundException; import it.inaf.ia2.transfer.exception.InsufficientStorageException; import it.inaf.ia2.transfer.exception.InvalidArgumentException; import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.FileDAO; Loading Loading @@ -64,14 +65,24 @@ public class PutFileController extends FileController { Optional<FileInfo> optFileInfo = fileDAO.getFileInfo(path); if (optFileInfo.isPresent()) { try (InputStream in = file != null ? file.getInputStream() : request.getInputStream()) { FileInfo fileInfo = optFileInfo.get(); String parentPath = fileInfo.getVirtualPath().substring(0, fileInfo.getVirtualPath().lastIndexOf("/")); Long remainingQuota = fileDAO.getRemainingQuota(parentPath); // if MultipartFile provides file size it is possible to check // quota limit before reading the stream if (remainingQuota != null && file != null && file.getSize() > remainingQuota) { throw new InsufficientStorageException(fileInfo.getVirtualPath()); } if (file != null) { fileInfo.setContentType(file.getContentType()); } fileInfo.setContentEncoding(contentEncoding); storeGenericFile(fileInfo, in, jobId); try (InputStream in = file != null ? file.getInputStream() : request.getInputStream()) { storeGenericFile(fileInfo, in, jobId, remainingQuota); } catch (IOException | NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } Loading @@ -81,7 +92,7 @@ public class PutFileController extends FileController { }, jobId); } private void storeGenericFile(FileInfo fileInfo, InputStream is, String jobId) throws IOException, NoSuchAlgorithmException { private void storeGenericFile(FileInfo fileInfo, InputStream is, String jobId, Long remainingQuota) throws IOException, NoSuchAlgorithmException { File file = new File(fileInfo.getOsPath()); Loading Loading @@ -112,6 +123,13 @@ public class PutFileController extends FileController { } Long fileSize = Files.size(file.toPath()); // Quota limit is checked again to handle cases where MultipartFile is not used if (remainingQuota != null && fileSize > remainingQuota) { file.delete(); throw new InsufficientStorageException(fileInfo.getVirtualPath()); } String md5Checksum = makeMD5Checksum(file); fileDAO.updateFileAttributes(fileInfo.getNodeId(), Loading
src/main/java/it/inaf/ia2/transfer/exception/InsufficientStorageException.java 0 → 100644 +18 −0 Original line number Diff line number Diff line /* * 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.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.INSUFFICIENT_STORAGE) public class InsufficientStorageException extends JobException { public InsufficientStorageException(String path) { super(Type.FATAL, "Quota Exceeded"); setErrorDetail("QuotaExceeded Path: " + path); } }
src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java +70 −4 Original line number Diff line number Diff line Loading @@ -5,14 +5,18 @@ */ package it.inaf.ia2.transfer.controller; import it.inaf.ia2.transfer.exception.InsufficientStorageException; 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.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Optional; import java.util.UUID; import javax.servlet.ServletInputStream; import net.ivoa.xml.uws.v1.ExecutionPhase; import org.assertj.core.util.Files; import org.junit.jupiter.api.AfterAll; Loading @@ -22,6 +26,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import org.mockito.Mockito; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; Loading Loading @@ -70,6 +76,8 @@ public class PutFileControllerTest { @Test public void putGenericFile() throws Exception { when(fileDao.getRemainingQuota(any())).thenReturn(null); String randomFileName = UUID.randomUUID().toString(); createBaseFileInfo(randomFileName); Loading Loading @@ -99,6 +107,8 @@ public class PutFileControllerTest { private void putGenericFileWithNameConflict(String name1, String name2, String name3) throws Exception { when(fileDao.getRemainingQuota(any())).thenReturn(null); createBaseFileInfo(name1); MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", "text/plain", "content".getBytes()); Loading Loading @@ -143,6 +153,8 @@ public class PutFileControllerTest { @Test public void putGenericFileWithJobId() throws Exception { when(fileDao.getRemainingQuota(any())).thenReturn(null); when(jobDAO.isJobExisting("pippo10")).thenReturn(false); when(jobDAO.isJobExisting("pippo5")).thenReturn(true); Loading Loading @@ -222,6 +234,59 @@ public class PutFileControllerTest { verify(jobDAO, times(1)).setJobError(eq("abcdef"), any()); } @Test public void testQuotaExceededMultipart() throws Exception { when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l); createBaseFileInfo(); MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", null, "content".getBytes()); Exception ex = mockMvc.perform(putMultipart("/path/to/test.txt") .file(fakeFile)) .andDo(print()) .andExpect(status().is5xxServerError()) .andReturn().getResolvedException(); verify(fileDao, times(1)).getRemainingQuota(eq("/path/to")); assertTrue(ex instanceof InsufficientStorageException); } @Test public void testQuotaExceededStream() throws Exception { when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l); createBaseFileInfo(); MockHttpServletRequestBuilder streamBuilder = put("/path/to/test.txt"); streamBuilder.with(new RequestPostProcessor() { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { MockHttpServletRequest spyRequest = spy(request); ByteArrayInputStream bais = new ByteArrayInputStream("some data".getBytes()); ServletInputStream sis = mock(ServletInputStream.class); try { when(sis.transferTo(any())).thenAnswer(i -> bais.transferTo(i.getArgument(0))); } catch (IOException ex) { } Mockito.doReturn(sis).when(spyRequest).getInputStream(); return spyRequest; } }); Exception ex = mockMvc.perform(streamBuilder) .andDo(print()) .andExpect(status().is5xxServerError()) .andReturn().getResolvedException(); verify(fileDao, times(1)).getRemainingQuota(eq("/path/to")); assertTrue(ex instanceof InsufficientStorageException); } private FileInfo createBaseFileInfo() { String randomFileName = UUID.randomUUID().toString(); return createBaseFileInfo(randomFileName); Loading @@ -230,6 +295,7 @@ public class PutFileControllerTest { private FileInfo createBaseFileInfo(String fileName) { FileInfo fileInfo = new FileInfo(); fileInfo.setOsPath(getTestFilePath(fileName)); fileInfo.setVirtualPath("/path/to/" + fileName); fileInfo.setPublic(false); when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo)); Loading