Migrate stream to resource for stability (#6160)

This commit is contained in:
Anthony Stirling
2026-04-23 15:56:31 +01:00
committed by GitHub
parent c294e9b2cb
commit 177c776658
109 changed files with 1390 additions and 855 deletions
@@ -10,6 +10,7 @@ import java.nio.file.Path;
import java.util.UUID; import java.util.UUID;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@@ -157,13 +158,48 @@ public class FileStorage {
success = true; success = true;
} finally { } finally {
if (!success) { if (!success) {
try {
Files.deleteIfExists(filePath); Files.deleteIfExists(filePath);
} catch (IOException cleanupEx) {
log.warn(
"Failed to clean up partial file {} after store failure",
filePath,
cleanupEx);
}
} }
} }
log.debug("Stored StreamingResponseBody with ID: {}", fileId); log.debug("Stored StreamingResponseBody with ID: {}", fileId);
return fileId; return fileId;
} }
/**
* Persist a {@link Resource} body to disk, returning the generated file ID. Used by the async
* job pipeline to capture {@code ResponseEntity<Resource>} results produced by controllers.
*/
public String storeFromResource(Resource resource, String originalName) throws IOException {
String fileId = generateFileId();
Path filePath = getFilePath(fileId);
Files.createDirectories(filePath.getParent());
boolean success = false;
try (InputStream in = resource.getInputStream()) {
Files.copy(in, filePath);
success = true;
} finally {
if (!success) {
try {
Files.deleteIfExists(filePath);
} catch (IOException cleanupEx) {
log.warn(
"Failed to clean up partial file {} after store failure",
filePath,
cleanupEx);
}
}
}
log.debug("Stored Resource with ID: {}", fileId);
return fileId;
}
/** /**
* Delete a file by its ID * Delete a file by its ID
* *
@@ -11,6 +11,7 @@ import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -321,6 +322,13 @@ public class JobExecutorService {
log.debug( log.debug(
"Stored ResponseEntity<StreamingResponseBody> result with fileId: {}", "Stored ResponseEntity<StreamingResponseBody> result with fileId: {}",
fileId); fileId);
} else if (body instanceof Resource resource) {
String filename = extractResponseFilename(response);
String contentType = extractResponseContentType(response);
String fileId = fileStorage.storeFromResource(resource, filename);
taskManager.setFileResult(jobId, fileId, filename, contentType);
log.debug("Stored ResponseEntity<Resource> result with fileId: {}", fileId);
} else { } else {
// Check if the response body contains a fileId // Check if the response body contains a fileId
if (body != null && body.toString().contains("fileId")) { if (body != null && body.toString().contains("fileId")) {
@@ -16,11 +16,11 @@ import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.util.data.MutableDataSet; import com.vladsch.flexmark.util.data.MutableDataSet;
@@ -49,7 +49,7 @@ public class PDFToFile {
this.runtimePathConfig = runtimePathConfig; this.runtimePathConfig = runtimePathConfig;
} }
public ResponseEntity<StreamingResponseBody> processPdfToMarkdown(MultipartFile inputFile) public ResponseEntity<Resource> processPdfToMarkdown(MultipartFile inputFile)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) { if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
@@ -169,7 +169,7 @@ public class PDFToFile {
return PATTERN.matcher(markdown).replaceAll("$1(images/$2)"); return PATTERN.matcher(markdown).replaceAll("$1(images/$2)");
} }
public ResponseEntity<StreamingResponseBody> processPdfToHtml(MultipartFile inputFile) public ResponseEntity<Resource> processPdfToHtml(MultipartFile inputFile)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) { if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
@@ -232,7 +232,7 @@ public class PDFToFile {
finalOut, fileName, MediaType.APPLICATION_OCTET_STREAM); finalOut, fileName, MediaType.APPLICATION_OCTET_STREAM);
} }
public ResponseEntity<StreamingResponseBody> processPdfToOfficeFormat( public ResponseEntity<Resource> processPdfToOfficeFormat(
MultipartFile inputFile, String outputFormat, String libreOfficeFilter) MultipartFile inputFile, String outputFormat, String libreOfficeFilter)
throws IOException, InterruptedException { throws IOException, InterruptedException {
@@ -1,19 +1,22 @@
package stirling.software.common.util; package stirling.software.common.util;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
@@ -49,12 +52,7 @@ public class WebResponseUtils {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType); headers.setContentType(mediaType);
headers.setContentLength(bytes.length); headers.setContentLength(bytes.length);
String encodedDocName = headers.setContentDispositionFormData("attachment", encodeAttachmentName(docName));
RegexPatternUtils.getInstance()
.getPlusSignPattern()
.matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8))
.replaceAll("%20");
headers.setContentDispositionFormData("attachment", encodedDocName);
return new ResponseEntity<>(bytes, headers, HttpStatus.OK); return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
} }
@@ -73,7 +71,17 @@ public class WebResponseUtils {
return baosToWebResponse(baos, docName); return baosToWebResponse(baos, docName);
} }
public static ResponseEntity<StreamingResponseBody> pdfDocToWebResponse( /**
* Save a {@link PDDocument} to a managed temp file and return it as a file-backed {@code
* ResponseEntity<Resource>}.
*
* <p>The returned {@link Resource} owns the supplied {@link TempFile} — the file is deleted
* when Spring closes the response {@link InputStream} after writing the body. This is a
* synchronous equivalent of the previous {@code StreamingResponseBody} pattern and avoids the
* async-dispatch hazards (response-committed races, filter incompatibility, silent write
* failures) that {@code StreamingResponseBody} introduced.
*/
public static ResponseEntity<Resource> pdfDocToWebResponse(
PDDocument document, String docName, TempFileManager tempFileManager) PDDocument document, String docName, TempFileManager tempFileManager)
throws IOException { throws IOException {
TempFile tempFile = tempFileManager.createManagedTempFile(".pdf"); TempFile tempFile = tempFileManager.createManagedTempFile(".pdf");
@@ -87,38 +95,49 @@ public class WebResponseUtils {
} }
/** /**
* Convert a File to a web response (PDF default). * Convert a {@link TempFile} holding a PDF into a web response.
*
* <p>The temp file is deleted when Spring closes the response body stream.
* *
* @param outputTempFile The temporary file to be sent as a response. * @param outputTempFile The temporary file to be sent as a response.
* @param docName The name of the document. * @param docName The name of the document.
* @return A ResponseEntity containing the file as a resource. * @return A ResponseEntity whose body streams the file, deleting it on close.
*/ */
public static ResponseEntity<StreamingResponseBody> pdfFileToWebResponse( public static ResponseEntity<Resource> pdfFileToWebResponse(
TempFile outputTempFile, String docName) throws IOException { TempFile outputTempFile, String docName) throws IOException {
return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_PDF); return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_PDF);
} }
/** /**
* Convert a File to a web response (ZIP default). * Convert a {@link TempFile} holding a ZIP archive into a web response.
*
* <p>The temp file is deleted when Spring closes the response body stream.
* *
* @param outputTempFile The temporary file to be sent as a response. * @param outputTempFile The temporary file to be sent as a response.
* @param docName The name of the document. * @param docName The name of the document.
* @return A ResponseEntity containing the file as a resource. * @return A ResponseEntity whose body streams the file, deleting it on close.
*/ */
public static ResponseEntity<StreamingResponseBody> zipFileToWebResponse( public static ResponseEntity<Resource> zipFileToWebResponse(
TempFile outputTempFile, String docName) throws IOException { TempFile outputTempFile, String docName) throws IOException {
return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_OCTET_STREAM); return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_OCTET_STREAM);
} }
/** /**
* Convert a File to a web response with explicit media type (e.g., ZIP). * Convert a {@link TempFile} into a web response with an explicit media type.
*
* <p>The returned {@link ResponseEntity} carries a {@link ManagedTempFileResource} as its body.
* Spring's {@code ResourceHttpMessageConverter} calls {@link Resource#getInputStream()} once
* and closes the returned stream after writing — at which point the underlying {@link TempFile}
* is deleted. Everything runs synchronously on the request thread, so write failures propagate
* through normal Spring error handling and are logged, rather than silently truncating the
* response.
* *
* @param outputTempFile The temporary file to be sent as a response. * @param outputTempFile The temporary file to be sent as a response.
* @param docName The name of the document. * @param docName The name of the document.
* @param mediaType The content type to set on the response. * @param mediaType The content type to set on the response.
* @return A ResponseEntity containing the file as a resource. * @return A ResponseEntity whose body streams the file, deleting it on close.
*/ */
public static ResponseEntity<StreamingResponseBody> fileToWebResponse( public static ResponseEntity<Resource> fileToWebResponse(
TempFile outputTempFile, String docName, MediaType mediaType) throws IOException { TempFile outputTempFile, String docName, MediaType mediaType) throws IOException {
try { try {
@@ -127,23 +146,9 @@ public class WebResponseUtils {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType); headers.setContentType(mediaType);
headers.setContentLength(len); headers.setContentLength(len);
String encodedDocName = headers.setContentDispositionFormData("attachment", encodeAttachmentName(docName));
RegexPatternUtils.getInstance()
.getPlusSignPattern()
.matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8))
.replaceAll("%20");
headers.setContentDispositionFormData("attachment", encodedDocName);
StreamingResponseBody body =
os -> {
try (os) {
Files.copy(path, os);
os.flush();
} finally {
outputTempFile.close();
}
};
Resource body = new ManagedTempFileResource(outputTempFile);
return new ResponseEntity<>(body, headers, HttpStatus.OK); return new ResponseEntity<>(body, headers, HttpStatus.OK);
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
try { try {
@@ -154,4 +159,115 @@ public class WebResponseUtils {
throw e; throw e;
} }
} }
private static String encodeAttachmentName(String docName) {
return RegexPatternUtils.getInstance()
.getPlusSignPattern()
.matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8))
.replaceAll("%20");
}
/**
* {@link Resource} backed by a {@link TempFile}. The underlying temp file is deleted when the
* response {@code InputStream} is closed — i.e. after Spring has finished writing the body. Any
* {@link IOException} during the copy is logged via {@link ClosingInputStream} and propagates
* through Spring's normal error path. Since response headers are committed before the body
* transfer begins, a mid-body failure manifests as a server-side log entry plus an aborted
* connection rather than a silently-truncated success — which is the behaviour this class was
* added to restore.
*
* <p><b>Single-use contract:</b> {@link #getInputStream()} is intended to be called once by
* Spring's {@code ResourceHttpMessageConverter} on the normal write path. After the returned
* stream is closed the backing temp file is deleted, so subsequent {@code getInputStream()}
* calls will either see a deleted file (tests that mock {@link TempFile#close()} are an
* exception) or fail at read time. Callers that need to re-read the body must copy it first.
*/
public static final class ManagedTempFileResource extends FileSystemResource {
private final TempFile tempFile;
public ManagedTempFileResource(TempFile tempFile) {
super(tempFile.getFile());
this.tempFile = tempFile;
}
@Override
public InputStream getInputStream() throws IOException {
InputStream source;
try {
source = super.getInputStream();
} catch (IOException e) {
// Opening the input stream already failed; make sure we don't leak the temp file.
try {
tempFile.close();
} catch (Exception closeEx) {
e.addSuppressed(closeEx);
}
throw e;
}
return new ClosingInputStream(source, tempFile);
}
}
/**
* Stream wrapper that deletes its backing {@link TempFile} on close. Logs — but does not
* swallow — any IOException that happens while reading the body, so upstream handlers can
* surface the failure to the client.
*/
private static final class ClosingInputStream extends FilterInputStream {
private final TempFile tempFile;
private boolean closed;
ClosingInputStream(InputStream delegate, TempFile tempFile) {
super(delegate);
this.tempFile = tempFile;
}
@Override
public int read() throws IOException {
try {
return super.read();
} catch (IOException e) {
log.error(
"Failed to read temp response body {} while streaming to client",
tempFile.getAbsolutePath(),
e);
throw e;
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
return super.read(b, off, len);
} catch (IOException e) {
log.error(
"Failed to read temp response body {} while streaming to client",
tempFile.getAbsolutePath(),
e);
throw e;
}
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
try {
super.close();
} finally {
try {
tempFile.close();
} catch (Exception closeEx) {
log.warn(
"Failed to clean up temp file {} after streaming response",
tempFile.getAbsolutePath(),
closeEx);
}
}
}
}
} }
@@ -4,8 +4,11 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -13,6 +16,8 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -185,4 +190,76 @@ class FileStorageTest {
// Assert // Assert
assertFalse(result); assertFalse(result);
} }
@Test
void storeFromResource_happyPath_persistsContent() throws IOException {
// Arrange
byte[] payload = "resource-body-bytes".getBytes(StandardCharsets.UTF_8);
Resource resource = new ByteArrayResource(payload);
// Act
String fileId = fileStorage.storeFromResource(resource, "whatever.pdf");
// Assert
assertNotNull(fileId);
assertTrue(Files.exists(tempDir.resolve(fileId)));
assertArrayEquals(payload, fileStorage.retrieveBytes(fileId));
}
@Test
void storeFromResource_failureCleansUpPartialFile() throws IOException {
// Arrange: a Resource whose getInputStream returns a stream that throws after
// emitting a few bytes. The finally block in storeFromResource should remove
// the partial file on disk.
byte[] head = "partial".getBytes(StandardCharsets.UTF_8);
Resource flakyResource =
new ByteArrayResource(head) {
@Override
public InputStream getInputStream() {
return new InputStream() {
private int position = 0;
@Override
public int read() throws IOException {
if (position < head.length) {
return head[position++] & 0xFF;
}
throw new IOException("simulated mid-copy read failure");
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (position >= head.length) {
throw new IOException("simulated mid-copy read failure");
}
int toCopy = Math.min(len, head.length - position);
System.arraycopy(head, position, b, off, toCopy);
position += toCopy;
return toCopy;
}
};
}
};
// Snapshot dir contents before the call so we can detect any lingering file.
long filesBefore;
try (Stream<Path> s = Files.list(tempDir)) {
filesBefore = s.count();
}
// Act + Assert: IOException must propagate out — not be swallowed.
assertThrows(
IOException.class, () -> fileStorage.storeFromResource(flakyResource, "n.pdf"));
// Assert: no partial file lingers under the storage directory — the finally
// branch's deleteIfExists must have cleaned it up.
long filesAfter;
try (Stream<Path> s = Files.list(tempDir)) {
filesAfter = s.count();
}
assertEquals(
filesBefore,
filesAfter,
"partial file must be cleaned up by storeFromResource finally block");
}
} }
@@ -8,9 +8,11 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@@ -24,7 +26,12 @@ import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -180,6 +187,46 @@ class JobExecutorServiceTest {
verify(spy).runJobGeneric(eq(false), any(Supplier.class), eq(customTimeout)); verify(spy).runJobGeneric(eq(false), any(Supplier.class), eq(customTimeout));
} }
@Test
void shouldPersistResponseEntityResourceBodyViaFileStorage() throws Exception {
// Given: an async job whose result is a ResponseEntity<Resource> — the new
// branch added by the stream-to-Resource migration. The executor must route
// the body through FileStorage.storeFromResource and then record the result
// via TaskManager.setFileResult with the filename/content-type extracted
// from the response headers.
byte[] payload = "resource-bytes".getBytes(StandardCharsets.UTF_8);
Resource resource = new ByteArrayResource(payload);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDisposition(
ContentDisposition.formData().name("attachment").filename("result.pdf").build());
Supplier<Object> work = () -> new ResponseEntity<>(resource, headers, HttpStatus.OK);
when(fileStorage.storeFromResource(any(Resource.class), anyString()))
.thenReturn("stored-file-id");
// When: run the job asynchronously — processJobResult runs on the executor.
ResponseEntity<?> response = jobExecutorService.runJobGeneric(true, work);
// Then: the immediate return must be the JobResponse envelope.
assertEquals(HttpStatus.OK, response.getStatusCode());
assertInstanceOf(JobResponse.class, response.getBody());
// Wait for async processing and verify the Resource branch was taken —
// FileStorage.storeFromResource was invoked with the same Resource instance,
// and TaskManager.setFileResult recorded the extracted filename + content-type.
verify(fileStorage, timeout(5000)).storeFromResource(eq(resource), eq("result.pdf"));
verify(taskManager, timeout(5000))
.setFileResult(
anyString(),
eq("stored-file-id"),
eq("result.pdf"),
eq(MediaType.APPLICATION_PDF_VALUE));
verify(taskManager, timeout(5000)).setComplete(anyString());
}
@Test @Test
void shouldHandleTimeout() throws Exception { void shouldHandleTimeout() throws Exception {
// Given // Given
@@ -25,12 +25,12 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.ZipSecurity; import io.github.pixee.security.ZipSecurity;
@@ -83,9 +83,11 @@ class PDFToFileTest {
pdfToFile = new PDFToFile(mockTempFileManager, mockRuntimePathConfig); pdfToFile = new PDFToFile(mockTempFileManager, mockRuntimePathConfig);
} }
private static byte[] drain(ResponseEntity<StreamingResponseBody> response) throws IOException { private static byte[] drain(ResponseEntity<Resource> response) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -100,7 +102,7 @@ class PDFToFileTest {
"This is not a PDF".getBytes()); "This is not a PDF".getBytes());
// Execute // Execute
ResponseEntity<StreamingResponseBody> response = pdfToFile.processPdfToMarkdown(nonPdfFile); ResponseEntity<Resource> response = pdfToFile.processPdfToMarkdown(nonPdfFile);
// Verify // Verify
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
@@ -117,7 +119,7 @@ class PDFToFileTest {
"This is not a PDF".getBytes()); "This is not a PDF".getBytes());
// Execute // Execute
ResponseEntity<StreamingResponseBody> response = pdfToFile.processPdfToHtml(nonPdfFile); ResponseEntity<Resource> response = pdfToFile.processPdfToHtml(nonPdfFile);
// Verify // Verify
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
@@ -135,7 +137,7 @@ class PDFToFileTest {
"This is not a PDF".getBytes()); "This is not a PDF".getBytes());
// Execute // Execute
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFile.processPdfToOfficeFormat(nonPdfFile, "docx", "draw_pdf_import"); pdfToFile.processPdfToOfficeFormat(nonPdfFile, "docx", "draw_pdf_import");
// Verify // Verify
@@ -154,7 +156,7 @@ class PDFToFileTest {
"Fake PDF content".getBytes()); "Fake PDF content".getBytes());
// Execute with invalid format // Execute with invalid format
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFile.processPdfToOfficeFormat(pdfFile, "invalid_format", "draw_pdf_import"); pdfToFile.processPdfToOfficeFormat(pdfFile, "invalid_format", "draw_pdf_import");
// Verify // Verify
@@ -205,8 +207,7 @@ class PDFToFileTest {
}); });
// Execute the method // Execute the method
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = pdfToFile.processPdfToMarkdown(pdfFile);
pdfToFile.processPdfToMarkdown(pdfFile);
// Verify - should now return a ZIP file instead of plain markdown // Verify - should now return a ZIP file instead of plain markdown
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -298,8 +299,7 @@ class PDFToFileTest {
}); });
// Execute the method // Execute the method
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = pdfToFile.processPdfToMarkdown(pdfFile);
pdfToFile.processPdfToMarkdown(pdfFile);
// Verify // Verify
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -377,7 +377,7 @@ class PDFToFileTest {
}); });
// Execute the method // Execute the method
ResponseEntity<StreamingResponseBody> response = pdfToFile.processPdfToHtml(pdfFile); ResponseEntity<Resource> response = pdfToFile.processPdfToHtml(pdfFile);
// Verify // Verify
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -463,7 +463,7 @@ class PDFToFileTest {
}); });
// Execute the method with docx format // Execute the method with docx format
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import"); pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import");
// Verify // Verify
@@ -535,7 +535,7 @@ class PDFToFileTest {
}); });
// Execute the method with ODP format // Execute the method with ODP format
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFile.processPdfToOfficeFormat(pdfFile, "odp", "draw_pdf_import"); pdfToFile.processPdfToOfficeFormat(pdfFile, "odp", "draw_pdf_import");
// Verify // Verify
@@ -620,7 +620,7 @@ class PDFToFileTest {
}); });
// Execute the method with text format // Execute the method with text format
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFile.processPdfToOfficeFormat(pdfFile, "txt:Text", "draw_pdf_import"); pdfToFile.processPdfToOfficeFormat(pdfFile, "txt:Text", "draw_pdf_import");
// Verify // Verify
@@ -679,7 +679,7 @@ class PDFToFileTest {
}); });
// Execute the method // Execute the method
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import"); pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import");
// Verify // Verify
@@ -726,7 +726,7 @@ class PDFToFileTest {
return mockExecutorResult; return mockExecutorResult;
}); });
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import"); pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import");
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -790,7 +790,7 @@ class PDFToFileTest {
return mockExecutorResult; return mockExecutorResult;
}); });
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import"); pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import");
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -3,17 +3,39 @@ package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import stirling.software.common.model.ApplicationProperties;
class WebResponseUtilsTest { class WebResponseUtilsTest {
@TempDir Path tempDir;
private TempFileManager tempFileManager;
@BeforeEach
void setUpTempFileManager() {
TempFileRegistry registry = new TempFileRegistry();
ApplicationProperties applicationProperties = new ApplicationProperties();
applicationProperties.getSystem().getTempFileManagement().setBaseTmpDir(tempDir.toString());
applicationProperties.getSystem().getTempFileManagement().setPrefix("wru-test-");
tempFileManager = new TempFileManager(registry, applicationProperties);
}
@Test @Test
void testBytesToWebResponse_defaultMediaType() throws IOException { void testBytesToWebResponse_defaultMediaType() throws IOException {
byte[] data = "test content".getBytes(StandardCharsets.UTF_8); byte[] data = "test content".getBytes(StandardCharsets.UTF_8);
@@ -91,4 +113,87 @@ class WebResponseUtilsTest {
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(0, response.getHeaders().getContentLength()); assertEquals(0, response.getHeaders().getContentLength());
} }
@Test
void managedTempFileResource_happyPathDeletesBackingFile() throws IOException {
// Create a real managed temp file on disk and populate it.
TempFile tempFile = tempFileManager.createManagedTempFile(".pdf");
byte[] payload = "fully-readable-pdf-body".getBytes(StandardCharsets.UTF_8);
Files.write(tempFile.getPath(), payload);
File backing = tempFile.getFile();
assertTrue(backing.exists(), "precondition: backing file should exist");
WebResponseUtils.ManagedTempFileResource resource =
new WebResponseUtils.ManagedTempFileResource(tempFile);
byte[] readBack;
try (InputStream in = resource.getInputStream()) {
readBack = in.readAllBytes();
}
assertArrayEquals(payload, readBack, "stream should deliver the original bytes");
assertFalse(
backing.exists(),
"backing temp file must be deleted once the response stream is closed");
}
@Test
void closingInputStream_propagatesReadFailure() throws IOException {
// ManagedTempFileResource is final, so we can't swap in a mock underlying stream.
// Instead: open the resource's stream, close the inner FileInputStream early
// (by closing the outer stream once), then confirm reading the now-closed stream
// throws — exercising ClosingInputStream's read() catch/log/rethrow path. Finally
// confirm the temp file is still cleaned up on close().
TempFile tempFile = tempFileManager.createManagedTempFile(".pdf");
Files.write(tempFile.getPath(), "some-bytes".getBytes(StandardCharsets.UTF_8));
File backing = tempFile.getFile();
assertTrue(backing.exists());
WebResponseUtils.ManagedTempFileResource resource =
new WebResponseUtils.ManagedTempFileResource(tempFile);
InputStream in = resource.getInputStream();
// Pre-close the underlying stream to guarantee read() throws.
in.close();
// After close, the temp file has been deleted already.
assertFalse(
backing.exists(),
"backing file is deleted on first close — precondition for read assertion");
// Any attempt to read from the already-closed stream must throw IOException
// (not silently return -1). This exercises the ClosingInputStream.read() path
// that logs and rethrows.
IOException ioex = assertThrows(IOException.class, in::read);
assertNotNull(ioex);
// close() is idempotent — a second call must not throw.
assertDoesNotThrow(in::close);
}
@Test
void managedTempFileResource_openFailureCleansUp() throws IOException {
// Arrange: build a TempFile whose backing file is deleted out from under it so
// super.getInputStream() throws on open. Instrument close() with an AtomicBoolean
// hook to confirm the cleanup path ran.
AtomicBoolean closed = new AtomicBoolean(false);
TempFile spying =
new TempFile(tempFileManager, ".pdf") {
@Override
public void close() {
closed.set(true);
super.close();
}
};
assertTrue(
spying.getFile().delete(),
"precondition: delete backing so super.getInputStream() fails");
WebResponseUtils.ManagedTempFileResource resource =
new WebResponseUtils.ManagedTempFileResource(spying);
assertThrows(IOException.class, resource::getInputStream);
assertTrue(
closed.get(),
"tempFile.close() must run when super.getInputStream() fails on open");
}
} }
@@ -12,13 +12,13 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -51,7 +51,7 @@ public class BookletImpositionController {
"This operation combines page reordering for booklet printing with multi-page layout. " "This operation combines page reordering for booklet printing with multi-page layout. "
+ "It rearranges pages in the correct order for booklet printing and places multiple pages " + "It rearranges pages in the correct order for booklet printing and places multiple pages "
+ "on each sheet for proper folding and binding. Input:PDF Output:PDF Type:SISO") + "on each sheet for proper folding and binding. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> createBookletImposition( public ResponseEntity<Resource> createBookletImposition(
@ModelAttribute BookletImpositionRequest request) throws IOException { @ModelAttribute BookletImpositionRequest request) throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
@@ -12,10 +12,10 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -132,7 +132,7 @@ public class CropController {
description = description =
"This operation takes an input PDF file and crops it according to the given" "This operation takes an input PDF file and crops it according to the given"
+ " coordinates. Input:PDF Output:PDF Type:SISO") + " coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> cropPdf(@ModelAttribute CropPdfForm request) public ResponseEntity<Resource> cropPdf(@ModelAttribute CropPdfForm request)
throws IOException { throws IOException {
if (request.isAutoCrop()) { if (request.isAutoCrop()) {
return cropWithAutomaticDetection(request); return cropWithAutomaticDetection(request);
@@ -153,8 +153,8 @@ public class CropController {
} }
} }
private ResponseEntity<StreamingResponseBody> cropWithAutomaticDetection( private ResponseEntity<Resource> cropWithAutomaticDetection(@ModelAttribute CropPdfForm request)
@ModelAttribute CropPdfForm request) throws IOException { throws IOException {
try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) { try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) {
try (PDDocument newDocument = try (PDDocument newDocument =
@@ -207,8 +207,8 @@ public class CropController {
} }
} }
private ResponseEntity<StreamingResponseBody> cropWithPDFBox( private ResponseEntity<Resource> cropWithPDFBox(@ModelAttribute CropPdfForm request)
@ModelAttribute CropPdfForm request) throws IOException { throws IOException {
try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) { try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) {
try (PDDocument newDocument = try (PDDocument newDocument =
@@ -263,8 +263,8 @@ public class CropController {
} }
} }
private ResponseEntity<StreamingResponseBody> cropWithGhostscript( private ResponseEntity<Resource> cropWithGhostscript(@ModelAttribute CropPdfForm request)
@ModelAttribute CropPdfForm request) throws IOException { throws IOException {
TempFile tempInputFile = null; TempFile tempInputFile = null;
TempFile tempOutputFile = null; TempFile tempOutputFile = null;
@@ -301,7 +301,7 @@ public class CropController {
processExecutor.runCommandWithOutputHandling(command); processExecutor.runCommandWithOutputHandling(command);
TempFile out = tempOutputFile; TempFile out = tempOutputFile;
tempOutputFile = null; // ownership transferred to StreamingResponseBody tempOutputFile = null; // ownership transferred to response Resource
return WebResponseUtils.pdfFileToWebResponse( return WebResponseUtils.pdfFileToWebResponse(
out, out,
GeneralUtils.generateFilename( GeneralUtils.generateFilename(
@@ -9,11 +9,11 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -151,7 +151,7 @@ public class EditTableOfContentsController {
@Operation( @Operation(
summary = "Edit Table of Contents", summary = "Edit Table of Contents",
description = "Add or edit bookmarks/table of contents in a PDF document.") description = "Add or edit bookmarks/table of contents in a PDF document.")
public ResponseEntity<StreamingResponseBody> editTableOfContents( public ResponseEntity<Resource> editTableOfContents(
@ModelAttribute EditTableOfContentsRequest request) throws Exception { @ModelAttribute EditTableOfContentsRequest request) throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
@@ -23,12 +23,12 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.XMPBasicSchema; import org.apache.xmpbox.schema.XMPBasicSchema;
import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.DomXmpParser;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -279,7 +279,7 @@ public class MergeController {
"This endpoint merges multiple PDF files into a single PDF file. The merged" "This endpoint merges multiple PDF files into a single PDF file. The merged"
+ " file will contain all pages from the input files in the order they were" + " file will contain all pages from the input files in the order they were"
+ " provided. Input:PDF Output:PDF Type:MISO") + " provided. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<StreamingResponseBody> mergePdfs( public ResponseEntity<Resource> mergePdfs(
@ModelAttribute MergePdfsRequest request, @ModelAttribute MergePdfsRequest request,
@RequestParam(value = "fileOrder", required = false) String fileOrder) @RequestParam(value = "fileOrder", required = false) String fileOrder)
throws IOException { throws IOException {
@@ -10,11 +10,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -47,7 +47,7 @@ public class MultiPageLayoutController {
description = description =
"This operation takes an input PDF file and the number of pages to merge into a" "This operation takes an input PDF file and the number of pages to merge into a"
+ " single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO") + " single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> mergeMultiplePagesIntoOne( public ResponseEntity<Resource> mergeMultiplePagesIntoOne(
@ModelAttribute MergeMultiplePagesRequest request) throws IOException { @ModelAttribute MergeMultiplePagesRequest request) throws IOException {
int MAX_PAGES = 100000; int MAX_PAGES = 100000;
@@ -11,11 +11,11 @@ import java.util.Map;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -46,8 +46,8 @@ public class PdfOverlayController {
description = description =
"Overlay PDF files onto a base PDF with different modes: Sequential," "Overlay PDF files onto a base PDF with different modes: Sequential,"
+ " Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO") + " Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO")
public ResponseEntity<StreamingResponseBody> overlayPdfs( public ResponseEntity<Resource> overlayPdfs(@ModelAttribute OverlayPdfsRequest request)
@ModelAttribute OverlayPdfsRequest request) throws IOException { throws IOException {
MultipartFile baseFile = request.getFileInput(); MultipartFile baseFile = request.getFileInput();
int overlayPos = request.getOverlayPosition(); int overlayPos = request.getOverlayPosition();
@@ -89,7 +89,7 @@ public class PdfOverlayController {
baseFile.getOriginalFilename(), "_overlayed.pdf"); baseFile.getOriginalFilename(), "_overlayed.pdf");
TempFile out = tempOut; TempFile out = tempOut;
tempOut = null; // ownership transferred to StreamingResponseBody tempOut = null; // ownership transferred to response Resource
return WebResponseUtils.pdfFileToWebResponse(out, outputFilename); return WebResponseUtils.pdfFileToWebResponse(out, outputFilename);
} }
} catch (Exception e) { } catch (Exception e) {
@@ -14,10 +14,10 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -52,7 +52,7 @@ public class PosterPdfController {
+ "suitable for printing on standard paper sizes (e.g., A4, Letter). " + "suitable for printing on standard paper sizes (e.g., A4, Letter). "
+ "Divides each page into a grid of smaller pages using Apache PDFBox. " + "Divides each page into a grid of smaller pages using Apache PDFBox. "
+ "Input: PDF Output: ZIP-PDF Type: SISO") + "Input: PDF Output: ZIP-PDF Type: SISO")
public ResponseEntity<StreamingResponseBody> posterPdf(@ModelAttribute PosterPdfRequest request) public ResponseEntity<Resource> posterPdf(@ModelAttribute PosterPdfRequest request)
throws Exception { throws Exception {
log.debug("Starting PDF poster split process with request: {}", request); log.debug("Starting PDF poster split process with request: {}", request);
@@ -8,11 +8,11 @@ import java.util.Locale;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -47,8 +47,8 @@ public class RearrangePagesPDFController {
"This endpoint removes specified pages from a given PDF file. Users can provide" "This endpoint removes specified pages from a given PDF file. Users can provide"
+ " a comma-separated list of page numbers or ranges to delete. Input:PDF" + " a comma-separated list of page numbers or ranges to delete. Input:PDF"
+ " Output:PDF Type:SISO") + " Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> deletePages( public ResponseEntity<Resource> deletePages(@ModelAttribute PDFWithPageNums request)
@ModelAttribute PDFWithPageNums request) throws IOException { throws IOException {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pagesToDelete = request.getPageNumbers(); String pagesToDelete = request.getPageNumbers();
@@ -228,8 +228,8 @@ public class RearrangePagesPDFController {
+ " order or custom mode. Users can provide a page order as a" + " order or custom mode. Users can provide a page order as a"
+ " comma-separated list of page numbers or page ranges, or a custom mode." + " comma-separated list of page numbers or page ranges, or a custom mode."
+ " Input:PDF Output:PDF") + " Input:PDF Output:PDF")
public ResponseEntity<StreamingResponseBody> rearrangePages( public ResponseEntity<Resource> rearrangePages(@ModelAttribute RearrangePagesRequest request)
@ModelAttribute RearrangePagesRequest request) throws IOException { throws IOException {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pageOrder = request.getPageNumbers(); String pageOrder = request.getPageNumbers();
String sortType = request.getCustomMode(); String sortType = request.getCustomMode();
@@ -5,11 +5,11 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -39,7 +39,7 @@ public class RotationController {
description = description =
"This endpoint rotates a given PDF file by a specified angle. The angle must be" "This endpoint rotates a given PDF file by a specified angle. The angle must be"
+ " a multiple of 90. Input:PDF Output:PDF Type:SISO") + " a multiple of 90. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> rotatePDF(@ModelAttribute RotatePDFRequest request) public ResponseEntity<Resource> rotatePDF(@ModelAttribute RotatePDFRequest request)
throws IOException { throws IOException {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle(); Integer angle = request.getAngle();
@@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -120,8 +120,8 @@ public class ScalePagesController {
description = description =
"This operation takes an input PDF file and the size to scale the pages to in" "This operation takes an input PDF file and the size to scale the pages to in"
+ " the output PDF file. Input:PDF Output:PDF Type:SISO") + " the output PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> scalePages( public ResponseEntity<Resource> scalePages(@ModelAttribute ScalePagesRequest request)
@ModelAttribute ScalePagesRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String targetPDRectangle = request.getPageSize(); String targetPDRectangle = request.getPageSize();
float scaleFactor = request.getScaleFactor(); float scaleFactor = request.getScaleFactor();
@@ -9,11 +9,11 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -48,7 +48,7 @@ public class SplitPDFController {
+ " specified page numbers or ranges. Users can specify pages using" + " specified page numbers or ranges. Users can specify pages using"
+ " individual numbers, ranges, or 'all' for every page. Input:PDF" + " individual numbers, ranges, or 'all' for every page. Input:PDF"
+ " Output:PDF Type:SIMO") + " Output:PDF Type:SIMO")
public ResponseEntity<StreamingResponseBody> splitPdf(@ModelAttribute SplitPagesRequest request) public ResponseEntity<Resource> splitPdf(@ModelAttribute SplitPagesRequest request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
@@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -127,8 +127,8 @@ public class SplitPdfByChaptersController {
description = description =
"Splits a PDF into chapters and returns a ZIP file. Input:PDF Output:ZIP-PDF" "Splits a PDF into chapters and returns a ZIP file. Input:PDF Output:ZIP-PDF"
+ " Type:SISO") + " Type:SISO")
public ResponseEntity<StreamingResponseBody> splitPdf( public ResponseEntity<Resource> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
@ModelAttribute SplitPdfByChaptersRequest request) throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean includeMetadata = Boolean.TRUE.equals(request.getIncludeMetadata()); boolean includeMetadata = Boolean.TRUE.equals(request.getIncludeMetadata());
@@ -15,11 +15,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -59,7 +59,7 @@ public class SplitPdfBySectionsController {
+ " which page to split, and how to split" + " which page to split, and how to split"
+ " ( halves, thirds, quarters, etc.), both vertically and horizontally." + " ( halves, thirds, quarters, etc.), both vertically and horizontally."
+ " Input:PDF Output:ZIP-PDF Type:SISO") + " Input:PDF Output:ZIP-PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> splitPdf( public ResponseEntity<Resource> splitPdf(
@Valid @ModelAttribute SplitPdfBySectionsRequest request) throws Exception { @Valid @ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String pageNumbers = request.getPageNumbers(); String pageNumbers = request.getPageNumbers();
@@ -8,11 +8,11 @@ import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -51,7 +51,7 @@ public class SplitPdfBySizeController {
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB" + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB"
+ " (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF" + " (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF"
+ " Output:ZIP-PDF Type:SISO") + " Output:ZIP-PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> autoSplitPdf( public ResponseEntity<Resource> autoSplitPdf(
@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception { @ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
log.debug("Starting PDF split process with request: {}", request); log.debug("Starting PDF split process with request: {}", request);
@@ -8,10 +8,10 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -44,7 +44,7 @@ public class ToSinglePageController {
+ " document. The width of the single page will be same as the input's" + " document. The width of the single page will be same as the input's"
+ " width, but the height will be the sum of all the pages' heights." + " width, but the height will be the sum of all the pages' heights."
+ " Input:PDF Output:PDF Type:SISO") + " Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> pdfToSinglePage(@ModelAttribute PDFFile request) public ResponseEntity<Resource> pdfToSinglePage(@ModelAttribute PDFFile request)
throws IOException { throws IOException {
// Load the source document // Load the source document
@@ -12,11 +12,11 @@ import java.util.Set;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -62,7 +62,7 @@ public class ConvertEbookToPDFController {
description = description =
"This endpoint converts common eBook formats (EPUB, MOBI, AZW3, FB2, TXT, DOCX)" "This endpoint converts common eBook formats (EPUB, MOBI, AZW3, FB2, TXT, DOCX)"
+ " to PDF using Calibre. Input:BOOK Output:PDF Type:SISO") + " to PDF using Calibre. Input:BOOK Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> convertEbookToPdf( public ResponseEntity<Resource> convertEbookToPdf(
@ModelAttribute ConvertEbookToPdfRequest request) throws Exception { @ModelAttribute ConvertEbookToPdfRequest request) throws Exception {
if (!isCalibreEnabled()) { if (!isCalibreEnabled()) {
throw new IllegalStateException("Calibre support is disabled"); throw new IllegalStateException("Calibre support is disabled");
@@ -162,7 +162,7 @@ public class ConvertEbookToPDFController {
document.save(tempOut.getFile()); document.save(tempOut.getFile());
} }
} }
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename); WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
tempOut = null; tempOut = null;
return response; return response;
@@ -6,12 +6,13 @@ import java.nio.file.Files;
import java.util.Locale; import java.util.Locale;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.HtmlUtils;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
@@ -51,8 +52,7 @@ public class ConvertEmlToPDF {
+ " with extensive customization options. Features include font settings," + " with extensive customization options. Features include font settings,"
+ " image constraints, display modes, attachment handling, and HTML debug" + " image constraints, display modes, attachment handling, and HTML debug"
+ " output. Input: EML or MSG file, Output: PDF or HTML file. Type: SISO") + " output. Input: EML or MSG file, Output: PDF or HTML file. Type: SISO")
public ResponseEntity<StreamingResponseBody> convertEmlToPdf( public ResponseEntity<Resource> convertEmlToPdf(@ModelAttribute EmlToPdfRequest request) {
@ModelAttribute EmlToPdfRequest request) {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String originalFilename = inputFile.getOriginalFilename(); String originalFilename = inputFile.getOriginalFilename();
@@ -159,14 +159,11 @@ public class ConvertEmlToPDF {
} }
} }
private ResponseEntity<StreamingResponseBody> errorResponse(HttpStatus status, String message) { private ResponseEntity<Resource> errorResponse(HttpStatus status, String message) {
byte[] body = message.getBytes(StandardCharsets.UTF_8); byte[] body = message.getBytes(StandardCharsets.UTF_8);
StreamingResponseBody streaming = return ResponseEntity.status(status)
os -> { .contentLength(body.length)
os.write(body); .body(new ByteArrayResource(body));
os.flush();
};
return ResponseEntity.status(status).body(streaming);
} }
private static @NotNull String buildErrorMessage(Exception e, String originalFilename) { private static @NotNull String buildErrorMessage(Exception e, String originalFilename) {
@@ -2,11 +2,11 @@ package stirling.software.SPDF.controller.api.converters;
import java.nio.file.Files; import java.nio.file.Files;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -40,7 +40,7 @@ public class ConvertHtmlToPDF {
description = description =
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format." "This endpoint takes an HTML or ZIP file input and converts it to a PDF format."
+ " Input:HTML Output:PDF Type:SISO") + " Input:HTML Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request) public ResponseEntity<Resource> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
throws Exception { throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
@@ -18,11 +18,11 @@ import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -274,8 +274,8 @@ public class ConvertImgPDFController {
description = description =
"This endpoint converts a CBZ (ZIP) comic book archive to a PDF file. " "This endpoint converts a CBZ (ZIP) comic book archive to a PDF file. "
+ "Input:CBZ Output:PDF Type:SISO") + "Input:CBZ Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> convertCbzToPdf( public ResponseEntity<Resource> convertCbzToPdf(@ModelAttribute ConvertCbzToPdfRequest request)
@ModelAttribute ConvertCbzToPdfRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean optimizeForEbook = request.isOptimizeForEbook(); boolean optimizeForEbook = request.isOptimizeForEbook();
@@ -300,8 +300,8 @@ public class ConvertImgPDFController {
description = description =
"This endpoint converts a PDF file to a CBZ (ZIP) comic book archive. " "This endpoint converts a PDF file to a CBZ (ZIP) comic book archive. "
+ "Input:PDF Output:CBZ Type:SISO") + "Input:PDF Output:CBZ Type:SISO")
public ResponseEntity<StreamingResponseBody> convertPdfToCbz( public ResponseEntity<Resource> convertPdfToCbz(@ModelAttribute ConvertPdfToCbzRequest request)
@ModelAttribute ConvertPdfToCbzRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
int dpi = request.getDpi(); int dpi = request.getDpi();
@@ -11,11 +11,11 @@ import org.commonmark.node.Node;
import org.commonmark.parser.Parser; import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -48,8 +48,8 @@ public class ConvertMarkdownToPdf {
description = description =
"This endpoint takes a Markdown file or ZIP (containing Markdown + images) input, converts it to HTML, and then to" "This endpoint takes a Markdown file or ZIP (containing Markdown + images) input, converts it to HTML, and then to"
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO") + " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> markdownToPdf( public ResponseEntity<Resource> markdownToPdf(@ModelAttribute GeneralFile generalFile)
@ModelAttribute GeneralFile generalFile) throws Exception { throws Exception {
MultipartFile fileInput = generalFile.getFileInput(); MultipartFile fileInput = generalFile.getFileInput();
if (fileInput == null) { if (fileInput == null) {
@@ -13,11 +13,11 @@ import java.util.Locale;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -206,8 +206,8 @@ public class ConvertOfficeController {
description = description =
"This endpoint converts a given file to a PDF using LibreOffice API Input:ANY" "This endpoint converts a given file to a PDF using LibreOffice API Input:ANY"
+ " Output:PDF Type:SISO") + " Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> processFileToPDF( public ResponseEntity<Resource> processFileToPDF(@ModelAttribute GeneralFile generalFile)
@ModelAttribute GeneralFile generalFile) throws Exception { throws Exception {
MultipartFile inputFile = generalFile.getFileInput(); MultipartFile inputFile = generalFile.getFileInput();
// unused but can start server instance if startup time is to long // unused but can start server instance if startup time is to long
// LibreOfficeListener.getInstance().start(); // LibreOfficeListener.getInstance().start();
@@ -223,7 +223,7 @@ public class ConvertOfficeController {
String filename = String filename =
GeneralUtils.generateFilename( GeneralUtils.generateFilename(
inputFile.getOriginalFilename(), "_convertedToPDF.pdf"); inputFile.getOriginalFilename(), "_convertedToPDF.pdf");
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
WebResponseUtils.pdfFileToWebResponse(tempOut, filename); WebResponseUtils.pdfFileToWebResponse(tempOut, filename);
tempOut = null; tempOut = null;
return response; return response;
@@ -9,11 +9,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -87,7 +87,7 @@ public class ConvertPDFToEpubController {
description = description =
"Convert a PDF file to a high-quality EPUB or AZW3 ebook using Calibre. Input:PDF" "Convert a PDF file to a high-quality EPUB or AZW3 ebook using Calibre. Input:PDF"
+ " Output:EPUB/AZW3 Type:SISO") + " Output:EPUB/AZW3 Type:SISO")
public ResponseEntity<StreamingResponseBody> convertPdfToEpub( public ResponseEntity<Resource> convertPdfToEpub(
@ModelAttribute ConvertPdfToEpubRequest request) throws Exception { @ModelAttribute ConvertPdfToEpubRequest request) throws Exception {
if (!endpointConfiguration.isGroupEnabled(CALIBRE_GROUP)) { if (!endpointConfiguration.isGroupEnabled(CALIBRE_GROUP)) {
@@ -12,10 +12,10 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.WorkbookUtil; import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -51,7 +51,7 @@ public class ConvertPDFToExcelController {
description = description =
"Extracts tabular data from each page of a PDF and writes it into an Excel" "Extracts tabular data from each page of a PDF and writes it into an Excel"
+ " workbook, one sheet per table. Input:PDF Output:XLSX Type:SISO") + " workbook, one sheet per table. Input:PDF Output:XLSX Type:SISO")
public ResponseEntity<StreamingResponseBody> pdfToExcel(@ModelAttribute PDFWithPageNums request) public ResponseEntity<Resource> pdfToExcel(@ModelAttribute PDFWithPageNums request)
throws Exception { throws Exception {
String baseName = String baseName =
GeneralUtils.removeExtension(request.getFileInput().getOriginalFilename()); GeneralUtils.removeExtension(request.getFileInput().getOriginalFilename());
@@ -1,10 +1,10 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -29,7 +29,7 @@ public class ConvertPDFToHtml {
summary = "Convert PDF to HTML", summary = "Convert PDF to HTML",
description = description =
"This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO") "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
public ResponseEntity<StreamingResponseBody> processPdfToHTML(@ModelAttribute PDFFile file) public ResponseEntity<Resource> processPdfToHTML(@ModelAttribute PDFFile file)
throws Exception { throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = file.getFileInput();
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig); PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
@@ -6,11 +6,11 @@ import java.nio.file.Files;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -44,7 +44,7 @@ public class ConvertPDFToOffice {
description = description =
"This endpoint converts a given PDF file to a Presentation format. Input:PDF" "This endpoint converts a given PDF file to a Presentation format. Input:PDF"
+ " Output:PPT Type:SISO") + " Output:PPT Type:SISO")
public ResponseEntity<StreamingResponseBody> processPdfToPresentation( public ResponseEntity<Resource> processPdfToPresentation(
@ModelAttribute PdfToPresentationRequest request) @ModelAttribute PdfToPresentationRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
@@ -59,7 +59,7 @@ public class ConvertPDFToOffice {
description = description =
"This endpoint converts a given PDF file to Text or RTF format. Input:PDF" "This endpoint converts a given PDF file to Text or RTF format. Input:PDF"
+ " Output:TXT Type:SISO") + " Output:TXT Type:SISO")
public ResponseEntity<StreamingResponseBody> processPdfToRTForTXT( public ResponseEntity<Resource> processPdfToRTForTXT(
@ModelAttribute PdfToTextOrRTFRequest request) @ModelAttribute PdfToTextOrRTFRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
@@ -89,8 +89,8 @@ public class ConvertPDFToOffice {
description = description =
"This endpoint converts a given PDF file to a Word document format. Input:PDF" "This endpoint converts a given PDF file to a Word document format. Input:PDF"
+ " Output:WORD Type:SISO") + " Output:WORD Type:SISO")
public ResponseEntity<StreamingResponseBody> processPdfToWord( public ResponseEntity<Resource> processPdfToWord(@ModelAttribute PdfToWordRequest request)
@ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat(); String outputFormat = request.getOutputFormat();
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig); PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
@@ -103,8 +103,7 @@ public class ConvertPDFToOffice {
description = description =
"This endpoint converts a PDF file to an XML file. Input:PDF Output:XML" "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML"
+ " Type:SISO") + " Type:SISO")
public ResponseEntity<StreamingResponseBody> processPdfToXML(@ModelAttribute PDFFile file) public ResponseEntity<Resource> processPdfToXML(@ModelAttribute PDFFile file) throws Exception {
throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = file.getFileInput();
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig); PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
@@ -71,13 +71,13 @@ import org.apache.xmpbox.schema.PDFAIdentificationSchema;
import org.apache.xmpbox.schema.XMPBasicSchema; import org.apache.xmpbox.schema.XMPBasicSchema;
import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpSerializer; import org.apache.xmpbox.xml.XmpSerializer;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -577,7 +577,7 @@ public class ConvertPDFToPDFA {
summary = "Convert a PDF to a PDF/A or PDF/X", summary = "Convert a PDF to a PDF/A or PDF/X",
description = description =
"This endpoint converts a PDF file to a PDF/A or PDF/X file using Ghostscript (preferred) or PDFBox/LibreOffice (fallback). PDF/A is a format designed for long-term archiving, while PDF/X is optimized for print production. Input:PDF Output:PDF Type:SISO") "This endpoint converts a PDF file to a PDF/A or PDF/X file using Ghostscript (preferred) or PDFBox/LibreOffice (fallback). PDF/A is a format designed for long-term archiving, while PDF/X is optimized for print production. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> pdfToPdfA(@ModelAttribute PdfToPdfARequest request) public ResponseEntity<Resource> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
throws Exception { throws Exception {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat(); String outputFormat = request.getOutputFormat();
@@ -613,7 +613,7 @@ public class ConvertPDFToPDFA {
return missing; return missing;
} }
private ResponseEntity<StreamingResponseBody> handlePdfXConversion( private ResponseEntity<Resource> handlePdfXConversion(
MultipartFile inputFile, String outputFormat) throws Exception { MultipartFile inputFile, String outputFormat) throws Exception {
PdfXProfile profile = PdfXProfile.fromRequest(outputFormat); PdfXProfile profile = PdfXProfile.fromRequest(outputFormat);
@@ -1806,7 +1806,7 @@ public class ConvertPDFToPDFA {
return Files.readAllBytes(outputPdf); return Files.readAllBytes(outputPdf);
} }
private ResponseEntity<StreamingResponseBody> handlePdfAConversion( private ResponseEntity<Resource> handlePdfAConversion(
MultipartFile inputFile, String outputFormat, boolean strict) throws Exception { MultipartFile inputFile, String outputFormat, boolean strict) throws Exception {
PdfaProfile profile = PdfaProfile.fromRequest(outputFormat); PdfaProfile profile = PdfaProfile.fromRequest(outputFormat);
@@ -1,12 +1,16 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -15,7 +19,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -56,7 +59,7 @@ public class ConvertPdfJsonController {
summary = "Convert PDF to Text Editor Format", summary = "Convert PDF to Text Editor Format",
description = description =
"Extracts PDF text, fonts, and metadata into an editable JSON structure for the text editor tool. Input:PDF Output:JSON Type:SISO") "Extracts PDF text, fonts, and metadata into an editable JSON structure for the text editor tool. Input:PDF Output:JSON Type:SISO")
public ResponseEntity<StreamingResponseBody> convertPdfToJson( public ResponseEntity<Resource> convertPdfToJson(
@ModelAttribute PDFFile request, @ModelAttribute PDFFile request,
@RequestParam(value = "lightweight", defaultValue = "false") boolean lightweight) @RequestParam(value = "lightweight", defaultValue = "false") boolean lightweight)
throws Exception { throws Exception {
@@ -65,10 +68,6 @@ public class ConvertPdfJsonController {
throw ExceptionUtils.createNullArgumentException("fileInput"); throw ExceptionUtils.createNullArgumentException("fileInput");
} }
// TODO: Refactor PdfJsonConversionService to write directly to an OutputStream
// instead of returning byte[], avoiding the intermediate heap allocation + temp file write
byte[] jsonBytes = pdfJsonConversionService.convertPdfToJson(inputFile, lightweight);
logJsonResponse("pdf/text-editor", jsonBytes);
String originalName = inputFile.getOriginalFilename(); String originalName = inputFile.getOriginalFilename();
String baseName = String baseName =
(originalName != null && !originalName.isBlank()) (originalName != null && !originalName.isBlank())
@@ -78,13 +77,19 @@ public class ConvertPdfJsonController {
: "document"; : "document";
String docName = baseName + ".json"; String docName = baseName + ".json";
TempFile tempOut = tempFileManager.createManagedTempFile(".json"); TempFile tempOut = tempFileManager.createManagedTempFile(".json");
try { try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
Files.write(tempOut.getPath(), jsonBytes); pdfJsonConversionService.convertPdfToJson(inputFile, lightweight, os);
} catch (Exception e) { } catch (Exception e) {
tempOut.close(); tempOut.close();
throw e; throw e;
} }
try {
logJsonResponse("pdf/text-editor", tempOut.getPath());
return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON);
} catch (Exception e) {
tempOut.close();
throw e;
}
} }
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/text-editor/pdf") @AutoJobPostMapping(consumes = "multipart/form-data", value = "/text-editor/pdf")
@@ -93,14 +98,13 @@ public class ConvertPdfJsonController {
summary = "Convert Text Editor Format to PDF", summary = "Convert Text Editor Format to PDF",
description = description =
"Rebuilds a PDF from the editable JSON structure generated by the text editor tool. Input:JSON Output:PDF Type:SISO") "Rebuilds a PDF from the editable JSON structure generated by the text editor tool. Input:JSON Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> convertJsonToPdf( public ResponseEntity<Resource> convertJsonToPdf(@ModelAttribute GeneralFile request)
@ModelAttribute GeneralFile request) throws Exception { throws Exception {
MultipartFile jsonFile = request.getFileInput(); MultipartFile jsonFile = request.getFileInput();
if (jsonFile == null) { if (jsonFile == null) {
throw ExceptionUtils.createNullArgumentException("fileInput"); throw ExceptionUtils.createNullArgumentException("fileInput");
} }
byte[] pdfBytes = pdfJsonConversionService.convertJsonToPdf(jsonFile);
String originalName = jsonFile.getOriginalFilename(); String originalName = jsonFile.getOriginalFilename();
String baseName = String baseName =
(originalName != null && !originalName.isBlank()) (originalName != null && !originalName.isBlank())
@@ -110,8 +114,8 @@ public class ConvertPdfJsonController {
: "document"; : "document";
String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf"; String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf"); TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
try { try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
Files.write(tempOut.getPath(), pdfBytes); pdfJsonConversionService.convertJsonToPdf(jsonFile, os);
} catch (Exception e) { } catch (Exception e) {
tempOut.close(); tempOut.close();
throw e; throw e;
@@ -126,7 +130,7 @@ public class ConvertPdfJsonController {
"Extracts document metadata, fonts, and page dimensions for the text editor tool. Caches the document for" "Extracts document metadata, fonts, and page dimensions for the text editor tool. Caches the document for"
+ " subsequent page requests. Returns a server-generated jobId scoped to the" + " subsequent page requests. Returns a server-generated jobId scoped to the"
+ " authenticated user. Input:PDF Output:JSON Type:SISO") + " authenticated user. Input:PDF Output:JSON Type:SISO")
public ResponseEntity<StreamingResponseBody> extractPdfMetadata(@ModelAttribute PDFFile request) public ResponseEntity<Resource> extractPdfMetadata(@ModelAttribute PDFFile request)
throws Exception { throws Exception {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
if (inputFile == null) { if (inputFile == null) {
@@ -139,30 +143,24 @@ public class ConvertPdfJsonController {
log.debug("Extracting metadata for PDF, assigned jobId: {}", scopedJobKey); log.debug("Extracting metadata for PDF, assigned jobId: {}", scopedJobKey);
byte[] jsonBytes =
pdfJsonConversionService.extractDocumentMetadata(inputFile, scopedJobKey);
logJsonResponse("pdf/text-editor/metadata", jsonBytes);
TempFile tempOut = tempFileManager.createManagedTempFile(".json"); TempFile tempOut = tempFileManager.createManagedTempFile(".json");
try { try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
Files.write(tempOut.getPath(), jsonBytes); pdfJsonConversionService.extractDocumentMetadata(inputFile, scopedJobKey, os);
} catch (Exception e) { } catch (Exception e) {
tempOut.close(); tempOut.close();
throw e; throw e;
} }
try {
logJsonResponse("pdf/text-editor/metadata", tempOut.getPath());
return ResponseEntity.ok() return ResponseEntity.ok()
.header("X-Job-Id", scopedJobKey) .header("X-Job-Id", scopedJobKey)
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.contentLength(java.nio.file.Files.size(tempOut.getPath())) .contentLength(Files.size(tempOut.getPath()))
.body( .body(new WebResponseUtils.ManagedTempFileResource(tempOut));
os -> { } catch (IOException | RuntimeException e) {
try (os) {
Files.copy(tempOut.getPath(), os);
os.flush();
} finally {
tempOut.close(); tempOut.close();
throw e;
} }
});
} }
@AutoJobPostMapping( @AutoJobPostMapping(
@@ -175,7 +173,7 @@ public class ConvertPdfJsonController {
"Applies edits for the specified pages of a cached PDF and returns an updated PDF." "Applies edits for the specified pages of a cached PDF and returns an updated PDF."
+ " Requires the PDF to have been previously cached via the text editor metadata endpoint." + " Requires the PDF to have been previously cached via the text editor metadata endpoint."
+ " The jobId must be obtained from the metadata extraction endpoint.") + " The jobId must be obtained from the metadata extraction endpoint.")
public ResponseEntity<StreamingResponseBody> exportPartialPdf( public ResponseEntity<Resource> exportPartialPdf(
@PathVariable String jobId, @PathVariable String jobId,
@RequestBody PdfJsonDocument document, @RequestBody PdfJsonDocument document,
@RequestParam(value = "filename", required = false) String filename) @RequestParam(value = "filename", required = false) String filename)
@@ -215,22 +213,26 @@ public class ConvertPdfJsonController {
"Retrieves a single page's content from a previously cached PDF document for the text editor tool." "Retrieves a single page's content from a previously cached PDF document for the text editor tool."
+ " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the" + " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the"
+ " authenticated user. Output:JSON") + " authenticated user. Output:JSON")
public ResponseEntity<StreamingResponseBody> extractSinglePage( public ResponseEntity<Resource> extractSinglePage(
@PathVariable String jobId, @PathVariable int pageNumber) throws Exception { @PathVariable String jobId, @PathVariable int pageNumber) throws Exception {
validateJobAccess(jobId); validateJobAccess(jobId);
byte[] jsonBytes = pdfJsonConversionService.extractSinglePage(jobId, pageNumber);
logJsonResponse("pdf/text-editor/page", jsonBytes);
String docName = "page_" + pageNumber + ".json"; String docName = "page_" + pageNumber + ".json";
TempFile tempOut = tempFileManager.createManagedTempFile(".json"); TempFile tempOut = tempFileManager.createManagedTempFile(".json");
try { try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
Files.write(tempOut.getPath(), jsonBytes); pdfJsonConversionService.extractSinglePage(jobId, pageNumber, os);
} catch (Exception e) { } catch (Exception e) {
tempOut.close(); tempOut.close();
throw e; throw e;
} }
try {
logJsonResponse("pdf/text-editor/page", tempOut.getPath());
return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON);
} catch (Exception e) {
tempOut.close();
throw e;
}
} }
@GetMapping(value = "/pdf/text-editor/fonts/{jobId}/{pageNumber}") @GetMapping(value = "/pdf/text-editor/fonts/{jobId}/{pageNumber}")
@@ -240,22 +242,26 @@ public class ConvertPdfJsonController {
"Retrieves the font payloads used by a single page from a previously cached PDF document." "Retrieves the font payloads used by a single page from a previously cached PDF document."
+ " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the" + " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the"
+ " authenticated user. Output:JSON") + " authenticated user. Output:JSON")
public ResponseEntity<StreamingResponseBody> extractPageFonts( public ResponseEntity<Resource> extractPageFonts(
@PathVariable String jobId, @PathVariable int pageNumber) throws Exception { @PathVariable String jobId, @PathVariable int pageNumber) throws Exception {
validateJobAccess(jobId); validateJobAccess(jobId);
byte[] jsonBytes = pdfJsonConversionService.extractPageFonts(jobId, pageNumber);
logJsonResponse("pdf/text-editor/fonts/page", jsonBytes);
String docName = "page_fonts_" + pageNumber + ".json"; String docName = "page_fonts_" + pageNumber + ".json";
TempFile tempOut = tempFileManager.createManagedTempFile(".json"); TempFile tempOut = tempFileManager.createManagedTempFile(".json");
try { try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
Files.write(tempOut.getPath(), jsonBytes); pdfJsonConversionService.extractPageFonts(jobId, pageNumber, os);
} catch (Exception e) { } catch (Exception e) {
tempOut.close(); tempOut.close();
throw e; throw e;
} }
try {
logJsonResponse("pdf/text-editor/fonts/page", tempOut.getPath());
return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON);
} catch (Exception e) {
tempOut.close();
throw e;
}
} }
@AutoJobPostMapping( @AutoJobPostMapping(
@@ -282,13 +288,34 @@ public class ConvertPdfJsonController {
return baseJobId; return baseJobId;
} }
private void logJsonResponse(String label, byte[] jsonBytes) { private void logJsonResponse(String label, Path jsonPath) {
if (jsonBytes == null) { if (jsonPath == null) {
log.warn("Returning {} JSON response: null bytes", label); log.warn("Returning {} JSON response: null path", label);
return; return;
} }
if (log.isDebugEnabled()) { boolean debugEnabled = log.isDebugEnabled();
boolean dumpEnabled = isPdfJsonDebugDumpEnabled();
boolean repeatScanEnabled = isPdfJsonRepeatScanEnabled();
// Reading the full file back from disk is only worth it for these diagnostic paths.
// The happy path (no debug flags) returns here immediately no extra IO.
if (!debugEnabled && !dumpEnabled && !repeatScanEnabled) {
return;
}
byte[] jsonBytes;
try {
jsonBytes = Files.readAllBytes(jsonPath);
} catch (IOException ex) {
log.warn(
"Failed to read PDF JSON ({}) for diagnostic logging: {}",
label,
ex.getMessage());
return;
}
if (debugEnabled) {
int length = jsonBytes.length; int length = jsonBytes.length;
boolean endsWithJson = boolean endsWithJson =
length > 0 && (jsonBytes[length - 1] == '}' || jsonBytes[length - 1] == ']'); length > 0 && (jsonBytes[length - 1] == '}' || jsonBytes[length - 1] == ']');
@@ -309,24 +336,23 @@ public class ConvertPdfJsonController {
tail); tail);
} }
if (isPdfJsonDebugDumpEnabled()) { if (dumpEnabled) {
try { try {
String tmpDir = System.getProperty("java.io.tmpdir"); String tmpDir = System.getProperty("java.io.tmpdir");
String customDir = System.getenv("SPDF_PDFJSON_DUMP_DIR"); String customDir = System.getenv("SPDF_PDFJSON_DUMP_DIR");
java.nio.file.Path dumpDir = Path dumpDir =
customDir != null && !customDir.isBlank() customDir != null && !customDir.isBlank()
? java.nio.file.Path.of(customDir) ? Path.of(customDir)
: java.nio.file.Path.of(tmpDir); : Path.of(tmpDir);
java.nio.file.Path dumpPath = Path dumpPath = Files.createTempFile(dumpDir, "pdfjson_", ".json");
java.nio.file.Files.createTempFile(dumpDir, "pdfjson_", ".json"); Files.copy(jsonPath, dumpPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
java.nio.file.Files.write(dumpPath, jsonBytes);
log.debug("PDF JSON debug dump ({}): {}", label, dumpPath); log.debug("PDF JSON debug dump ({}): {}", label, dumpPath);
} catch (Exception ex) { } catch (Exception ex) {
log.warn("Failed to write PDF JSON debug dump ({}): {}", label, ex.getMessage()); log.warn("Failed to write PDF JSON debug dump ({}): {}", label, ex.getMessage());
} }
} }
if (isPdfJsonRepeatScanEnabled()) { if (repeatScanEnabled) {
logRepeatedJsonStrings(label, jsonBytes); logRepeatedJsonStrings(label, jsonBytes);
} }
} }
@@ -9,12 +9,13 @@ import java.util.Locale;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -54,8 +55,7 @@ public class ConvertSvgToPDF {
+ "SVG dimensions (width/height) determine the PDF page size; defaults to A4 if not specified. " + "SVG dimensions (width/height) determine the PDF page size; defaults to A4 if not specified. "
+ "SVG content is sanitized to prevent XSS attacks. " + "SVG content is sanitized to prevent XSS attacks. "
+ "Input: SVG file(s), Output: PDF file(s) or ZIP. Type: MIMO") + "Input: SVG file(s), Output: PDF file(s) or ZIP. Type: MIMO")
public ResponseEntity<StreamingResponseBody> convertSvgToPdf( public ResponseEntity<Resource> convertSvgToPdf(@ModelAttribute SvgToPdfRequest request) {
@ModelAttribute SvgToPdfRequest request) {
MultipartFile[] inputFiles = request.getFileInput(); MultipartFile[] inputFiles = request.getFileInput();
boolean combineIntoSinglePdf = Boolean.TRUE.equals(request.getCombineIntoSinglePdf()); boolean combineIntoSinglePdf = Boolean.TRUE.equals(request.getCombineIntoSinglePdf());
@@ -95,10 +95,7 @@ public class ConvertSvgToPDF {
filenames.add(Filenames.toSimpleFileName(originalFilename)); filenames.add(Filenames.toSimpleFileName(originalFilename));
} catch (IOException e) { } catch (IOException e) {
log.error( log.error("SVG sanitization/reading failed for {}", originalFilename, e);
"SVG sanitization/reading failed for {}: {}",
originalFilename,
e.getMessage());
} }
} }
@@ -121,17 +118,14 @@ public class ConvertSvgToPDF {
} }
} }
private ResponseEntity<StreamingResponseBody> errorResponse(HttpStatus status, String message) { private ResponseEntity<Resource> errorResponse(HttpStatus status, String message) {
byte[] body = message.getBytes(StandardCharsets.UTF_8); byte[] body = message.getBytes(StandardCharsets.UTF_8);
StreamingResponseBody streaming = return ResponseEntity.status(status)
os -> { .contentLength(body.length)
os.write(body); .body(new ByteArrayResource(body));
os.flush();
};
return ResponseEntity.status(status).body(streaming);
} }
private ResponseEntity<StreamingResponseBody> handleCombinedConversion( private ResponseEntity<Resource> handleCombinedConversion(
List<byte[]> sanitizedSvgs, List<String> filenames) { List<byte[]> sanitizedSvgs, List<String> filenames) {
try { try {
log.info("Combining {} SVG files into single PDF", sanitizedSvgs.size()); log.info("Combining {} SVG files into single PDF", sanitizedSvgs.size());
@@ -169,7 +163,7 @@ public class ConvertSvgToPDF {
} }
} }
private ResponseEntity<StreamingResponseBody> handleSeparateConversion( private ResponseEntity<Resource> handleSeparateConversion(
List<byte[]> sanitizedSvgs, List<String> filenames) { List<byte[]> sanitizedSvgs, List<String> filenames) {
List<ConvertedPdf> convertedPdfs = new ArrayList<>(); List<ConvertedPdf> convertedPdfs = new ArrayList<>();
@@ -10,10 +10,10 @@ import java.util.Locale;
import java.util.Set; import java.util.Set;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -51,7 +51,7 @@ public class PdfVectorExportController {
description = description =
"Converts PostScript vector inputs (PS, EPS, EPSF) to PDF using Ghostscript." "Converts PostScript vector inputs (PS, EPS, EPSF) to PDF using Ghostscript."
+ " Input:PS/EPS Output:PDF Type:SISO") + " Input:PS/EPS Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> convertGhostscriptInputsToPdf( public ResponseEntity<Resource> convertGhostscriptInputsToPdf(
@Valid @ModelAttribute PdfVectorExportRequest request) throws Exception { @Valid @ModelAttribute PdfVectorExportRequest request) throws Exception {
String originalName = String originalName =
@@ -98,7 +98,7 @@ public class PdfVectorExportController {
description = description =
"Converts PDF to Ghostscript vector formats (EPS, PS, PCL, or XPS)." "Converts PDF to Ghostscript vector formats (EPS, PS, PCL, or XPS)."
+ " Input:PDF Output:VECTOR Type:SISO") + " Input:PDF Output:VECTOR Type:SISO")
public ResponseEntity<StreamingResponseBody> convertPdfToVector( public ResponseEntity<Resource> convertPdfToVector(
@Valid @ModelAttribute PdfVectorExportRequest request) throws Exception { @Valid @ModelAttribute PdfVectorExportRequest request) throws Exception {
String originalName = String originalName =
@@ -5,11 +5,11 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -56,8 +56,8 @@ public class FilterController {
description = "PDF did not pass filter", description = "PDF did not pass filter",
content = @Content()) content = @Content())
}) })
public ResponseEntity<StreamingResponseBody> containsText( public ResponseEntity<Resource> containsText(@ModelAttribute ContainsTextRequest request)
@ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String text = request.getText(); String text = request.getText();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
@@ -89,8 +89,8 @@ public class FilterController {
description = "PDF did not pass filter", description = "PDF did not pass filter",
content = @Content()) content = @Content())
}) })
public ResponseEntity<StreamingResponseBody> containsImage( public ResponseEntity<Resource> containsImage(@ModelAttribute PDFWithPageNums request)
@ModelAttribute PDFWithPageNums request) throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
@@ -10,6 +10,7 @@ import java.util.Map;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -18,7 +19,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.opencsv.CSVWriter; import com.opencsv.CSVWriter;
@@ -63,7 +63,7 @@ public class FormFillController {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final TempFileManager tempFileManager; private final TempFileManager tempFileManager;
private ResponseEntity<StreamingResponseBody> saveDocument(PDDocument document, String baseName) private ResponseEntity<Resource> saveDocument(PDDocument document, String baseName)
throws IOException { throws IOException {
return WebResponseUtils.pdfDocToWebResponse(document, baseName + ".pdf", tempFileManager); return WebResponseUtils.pdfDocToWebResponse(document, baseName + ".pdf", tempFileManager);
} }
@@ -262,7 +262,7 @@ public class FormFillController {
summary = "Modify existing form fields", summary = "Modify existing form fields",
description = description =
"Updates existing fields in the provided PDF and returns the updated file") "Updates existing fields in the provided PDF and returns the updated file")
public ResponseEntity<StreamingResponseBody> modifyFields( public ResponseEntity<Resource> modifyFields(
@Parameter( @Parameter(
description = "The input PDF file", description = "The input PDF file",
required = true, required = true,
@@ -293,7 +293,7 @@ public class FormFillController {
@Operation( @Operation(
summary = "Delete form fields", summary = "Delete form fields",
description = "Removes the specified fields from the PDF and returns the updated file") description = "Removes the specified fields from the PDF and returns the updated file")
public ResponseEntity<StreamingResponseBody> deleteFields( public ResponseEntity<Resource> deleteFields(
@Parameter( @Parameter(
description = "The input PDF file", description = "The input PDF file",
required = true, required = true,
@@ -329,7 +329,7 @@ public class FormFillController {
description = description =
"Populates the supplied PDF form using values from the provided JSON payload" "Populates the supplied PDF form using values from the provided JSON payload"
+ " and returns the filled PDF") + " and returns the filled PDF")
public ResponseEntity<StreamingResponseBody> fillForm( public ResponseEntity<Resource> fillForm(
@Parameter( @Parameter(
description = "The input PDF file", description = "The input PDF file",
required = true, required = true,
@@ -356,7 +356,7 @@ public class FormFillController {
document -> FormUtils.applyFieldValues(document, values, flatten, true)); document -> FormUtils.applyFieldValues(document, values, flatten, true));
} }
private ResponseEntity<StreamingResponseBody> processSingleFile( private ResponseEntity<Resource> processSingleFile(
MultipartFile file, String suffix, DocumentProcessor processor) throws IOException { MultipartFile file, String suffix, DocumentProcessor processor) throws IOException {
requirePdf(file); requirePdf(file);
@@ -6,11 +6,11 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -54,8 +54,8 @@ public class AttachmentController {
summary = "Add attachments to PDF", summary = "Add attachments to PDF",
description = description =
"This endpoint adds attachments to a PDF. Input:PDF, Output:PDF Type:MISO") "This endpoint adds attachments to a PDF. Input:PDF, Output:PDF Type:MISO")
public ResponseEntity<StreamingResponseBody> addAttachments( public ResponseEntity<Resource> addAttachments(@ModelAttribute AddAttachmentRequest request)
@ModelAttribute AddAttachmentRequest request) throws Exception { throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
List<MultipartFile> attachments = request.getAttachments(); List<MultipartFile> attachments = request.getAttachments();
boolean convertToPdfA3b = request.isConvertToPdfA3b(); boolean convertToPdfA3b = request.isConvertToPdfA3b();
@@ -143,7 +143,7 @@ public class AttachmentController {
description = description =
"This endpoint extracts all embedded attachments from a PDF into a ZIP archive." "This endpoint extracts all embedded attachments from a PDF into a ZIP archive."
+ " Input:PDF Output:ZIP Type:SISO") + " Input:PDF Output:ZIP Type:SISO")
public ResponseEntity<StreamingResponseBody> extractAttachments( public ResponseEntity<Resource> extractAttachments(
@ModelAttribute ExtractAttachmentsRequest request) throws IOException { @ModelAttribute ExtractAttachmentsRequest request) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(request, true)) { try (PDDocument document = pdfDocumentFactory.load(request, true)) {
Optional<byte[]> extracted = pdfAttachmentService.extractAttachments(document); Optional<byte[]> extracted = pdfAttachmentService.extractAttachments(document);
@@ -195,7 +195,7 @@ public class AttachmentController {
summary = "Rename attachment in PDF", summary = "Rename attachment in PDF",
description = description =
"This endpoint renames an embedded attachment in a PDF. Input:PDF Output:PDF Type:MISO") "This endpoint renames an embedded attachment in a PDF. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<StreamingResponseBody> renameAttachment( public ResponseEntity<Resource> renameAttachment(
@ModelAttribute RenameAttachmentRequest request) throws Exception { @ModelAttribute RenameAttachmentRequest request) throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String attachmentName = request.getAttachmentName(); String attachmentName = request.getAttachmentName();
@@ -230,7 +230,7 @@ public class AttachmentController {
summary = "Delete attachment from PDF", summary = "Delete attachment from PDF",
description = description =
"This endpoint deletes an embedded attachment from a PDF. Input:PDF Output:PDF Type:MISO") "This endpoint deletes an embedded attachment from a PDF. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<StreamingResponseBody> deleteAttachment( public ResponseEntity<Resource> deleteAttachment(
@ModelAttribute DeleteAttachmentRequest request) throws Exception { @ModelAttribute DeleteAttachmentRequest request) throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String attachmentName = request.getAttachmentName(); String attachmentName = request.getAttachmentName();
@@ -8,11 +8,11 @@ import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.text.TextPosition;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -45,8 +45,8 @@ public class AutoRenameController {
description = description =
"This endpoint accepts a PDF file and attempts to extract its title or header" "This endpoint accepts a PDF file and attempts to extract its title or header"
+ " based on heuristics. Input:PDF Output:PDF Type:SISO") + " based on heuristics. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> extractHeader( public ResponseEntity<Resource> extractHeader(@ModelAttribute ExtractHeaderRequest request)
@ModelAttribute ExtractHeaderRequest request) throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean useFirstTextAsFallback = Boolean.TRUE.equals(request.getUseFirstTextAsFallback()); boolean useFirstTextAsFallback = Boolean.TRUE.equals(request.getUseFirstTextAsFallback());
@@ -18,11 +18,11 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.google.zxing.*; import com.google.zxing.*;
import com.google.zxing.common.GlobalHistogramBinarizer; import com.google.zxing.common.GlobalHistogramBinarizer;
@@ -276,8 +276,8 @@ public class AutoSplitPdfController {
+ " splits the document at the QR code boundaries. The output is a zip" + " splits the document at the QR code boundaries. The output is a zip"
+ " file containing each separate PDF document. Input:PDF Output:ZIP-PDF" + " file containing each separate PDF document. Input:PDF Output:ZIP-PDF"
+ " Type:SISO") + " Type:SISO")
public ResponseEntity<StreamingResponseBody> autoSplitPdf( public ResponseEntity<Resource> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
@ModelAttribute AutoSplitPdfRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean duplexMode = Boolean.TRUE.equals(request.getDuplexMode()); boolean duplexMode = Boolean.TRUE.equals(request.getDuplexMode());
@@ -15,12 +15,12 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -88,7 +88,7 @@ public class BlankPageController {
"This endpoint removes blank pages from a given PDF file. Users can specify the" "This endpoint removes blank pages from a given PDF file. Users can specify the"
+ " threshold and white percentage to tune the detection of blank pages." + " threshold and white percentage to tune the detection of blank pages."
+ " Input:PDF Output:PDF Type:SISO") + " Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> removeBlankPages( public ResponseEntity<Resource> removeBlankPages(
@ModelAttribute RemoveBlankPagesRequest request) @ModelAttribute RemoveBlankPagesRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
@@ -32,13 +32,13 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImage; import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -928,8 +928,8 @@ public class CompressController {
description = description =
"This endpoint accepts a PDF file and optimizes it based on the provided" "This endpoint accepts a PDF file and optimizes it based on the provided"
+ " parameters. Input:PDF Output:PDF Type:SISO") + " parameters. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> optimizePdf( public ResponseEntity<Resource> optimizePdf(@ModelAttribute OptimizePdfRequest request)
@ModelAttribute OptimizePdfRequest request) throws Exception { throws Exception {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
// Validate input file // Validate input file
@@ -9,11 +9,11 @@ import org.apache.pdfbox.cos.*;
import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdfwriter.compress.CompressParameters; import org.apache.pdfbox.pdfwriter.compress.CompressParameters;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -42,7 +42,7 @@ public class DecompressPdfController {
@Operation( @Operation(
summary = "Decompress PDF streams", summary = "Decompress PDF streams",
description = "Fully decompresses all PDF streams including text content") description = "Fully decompresses all PDF streams including text content")
public ResponseEntity<StreamingResponseBody> decompressPdf(@ModelAttribute PDFFile request) public ResponseEntity<Resource> decompressPdf(@ModelAttribute PDFFile request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
@@ -17,11 +17,11 @@ import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -65,7 +65,7 @@ public class ExtractImageScansController {
+ " parameters. Users can specify angle threshold, tolerance, minimum area," + " parameters. Users can specify angle threshold, tolerance, minimum area,"
+ " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP" + " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP"
+ " Type:SIMO") + " Type:SIMO")
public ResponseEntity<StreamingResponseBody> extractImageScans( public ResponseEntity<Resource> extractImageScans(
@ModelAttribute ExtractImageScansRequest request) @ModelAttribute ExtractImageScansRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
@@ -198,7 +198,7 @@ public class ExtractImageScansController {
} }
} }
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
WebResponseUtils.zipFileToWebResponse(finalOutput, outputZipFilename); WebResponseUtils.zipFileToWebResponse(finalOutput, outputZipFilename);
finalOutputOwnershipTransferred = true; finalOutputOwnershipTransferred = true;
return response; return response;
@@ -215,7 +215,7 @@ public class ExtractImageScansController {
out.write(imageBytes); out.write(imageBytes);
} }
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
WebResponseUtils.fileToWebResponse( WebResponseUtils.fileToWebResponse(
finalOutput, finalOutput,
GeneralUtils.generateFilename(fileName, ".png"), GeneralUtils.generateFilename(fileName, ".png"),
@@ -19,11 +19,11 @@ import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -57,8 +57,8 @@ public class ExtractImagesController {
"This endpoint extracts images from a given PDF file and returns them in a zip" "This endpoint extracts images from a given PDF file and returns them in a zip"
+ " file. Users can specify the output image format. Input:PDF" + " file. Users can specify the output image format. Input:PDF"
+ " Output:IMAGE/ZIP Type:SIMO") + " Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity<StreamingResponseBody> extractImages( public ResponseEntity<Resource> extractImages(@ModelAttribute PDFExtractImagesRequest request)
@ModelAttribute PDFExtractImagesRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String imageFormat = request.getFormat(); String imageFormat = request.getFormat();
@@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -49,7 +49,7 @@ public class FlattenController {
description = description =
"Flattening just PDF form fields or converting each page to images to make text" "Flattening just PDF form fields or converting each page to images to make text"
+ " unselectable. Input:PDF, Output:PDF. Type:SISO") + " unselectable. Input:PDF, Output:PDF. Type:SISO")
public ResponseEntity<StreamingResponseBody> flatten(@ModelAttribute FlattenRequest request) public ResponseEntity<Resource> flatten(@ModelAttribute FlattenRequest request)
throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
@@ -8,12 +8,12 @@ import java.util.Map.Entry;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -64,7 +64,7 @@ public class MetadataController {
"This endpoint allows you to update the metadata of a given PDF file. You can" "This endpoint allows you to update the metadata of a given PDF file. You can"
+ " add, modify, or delete standard and custom metadata fields. Input:PDF" + " add, modify, or delete standard and custom metadata fields. Input:PDF"
+ " Output:PDF Type:SISO") + " Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> metadata(@ModelAttribute MetadataRequest request) public ResponseEntity<Resource> metadata(@ModelAttribute MetadataRequest request)
throws IOException { throws IOException {
// Extract PDF file from the request object // Extract PDF file from the request object
@@ -21,11 +21,11 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -90,7 +90,7 @@ public class OCRController {
+ " specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType," + " specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType,"
+ " and removeImagesAfter options. Uses OCRmyPDF if available, falls back to" + " and removeImagesAfter options. Uses OCRmyPDF if available, falls back to"
+ " Tesseract. Input:PDF Output:PDF Type:SI-Conditional") + " Tesseract. Input:PDF Output:PDF Type:SI-Conditional")
public ResponseEntity<StreamingResponseBody> processPdfWithOCR( public ResponseEntity<Resource> processPdfWithOCR(
@ModelAttribute ProcessPdfWithOcrRequest request) @ModelAttribute ProcessPdfWithOcrRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
@@ -194,13 +194,13 @@ public class OCRController {
// The intermediate PDF temp file is no longer needed; only the zip is streamed. // The intermediate PDF temp file is no longer needed; only the zip is streamed.
tempOutputFile.close(); tempOutputFile.close();
pdfOwnershipTransferred = true; pdfOwnershipTransferred = true;
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
WebResponseUtils.fileToWebResponse( WebResponseUtils.fileToWebResponse(
tempZipFile, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); tempZipFile, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
zipOwnershipTransferred = true; zipOwnershipTransferred = true;
return response; return response;
} else { } else {
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response =
WebResponseUtils.pdfFileToWebResponse(tempOutputFile, outputFilename); WebResponseUtils.pdfFileToWebResponse(tempOutputFile, outputFilename);
pdfOwnershipTransferred = true; pdfOwnershipTransferred = true;
return response; return response;
@@ -6,12 +6,12 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -45,8 +45,7 @@ public class OverlayImageController {
+ "SVG files are rendered as vector graphics for crisp output at any resolution. " + "SVG files are rendered as vector graphics for crisp output at any resolution. "
+ "The image can be overlaid on every page of the PDF if specified. " + "The image can be overlaid on every page of the PDF if specified. "
+ "Input:PDF/IMAGE/SVG Output:PDF Type:SISO") + "Input:PDF/IMAGE/SVG Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> overlayImage( public ResponseEntity<Resource> overlayImage(@ModelAttribute OverlayImageRequest request) {
@ModelAttribute OverlayImageRequest request) {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
MultipartFile imageFile = request.getImageFile(); MultipartFile imageFile = request.getImageFile();
float x = request.getX(); float x = request.getX();
@@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -46,8 +46,8 @@ public class PageNumbersController {
description = description =
"This operation takes an input PDF file and adds page numbers to it. Input:PDF" "This operation takes an input PDF file and adds page numbers to it. Input:PDF"
+ " Output:PDF Type:SISO") + " Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> addPageNumbers( public ResponseEntity<Resource> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
@ModelAttribute AddPageNumbersRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String customMargin = request.getCustomMargin(); String customMargin = request.getCustomMargin();
@@ -12,11 +12,11 @@ import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -47,7 +47,7 @@ public class RemoveImagesController {
description = description =
"This endpoint removes all embedded images from a PDF file and returns the" "This endpoint removes all embedded images from a PDF file and returns the"
+ " modified document. Input:PDF Output:PDF Type:SISO") + " modified document. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> removeImages(@ModelAttribute PDFFile request) public ResponseEntity<Resource> removeImages(@ModelAttribute PDFFile request)
throws IOException { throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
@@ -4,11 +4,11 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -54,7 +54,7 @@ public class RepairController {
"This endpoint repairs a given PDF file by running Ghostscript (primary), qpdf (fallback), or PDFBox (if no external tools available). The PDF is" "This endpoint repairs a given PDF file by running Ghostscript (primary), qpdf (fallback), or PDFBox (if no external tools available). The PDF is"
+ " first saved to a temporary location, repaired, read back, and then" + " first saved to a temporary location, repaired, read back, and then"
+ " returned as a response. Input:PDF Output:PDF Type:SISO") + " returned as a response. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> repairPdf(@ModelAttribute PDFFile file) public ResponseEntity<Resource> repairPdf(@ModelAttribute PDFFile file)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = file.getFileInput();
@@ -6,10 +6,10 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -39,7 +39,7 @@ public class ReplaceAndInvertColorController {
description = description =
"This endpoint accepts a PDF file and provides options to invert all colors, replace" "This endpoint accepts a PDF file and provides options to invert all colors, replace"
+ " text and background colors, or convert to CMYK color space for printing. Input:PDF Output:PDF Type:SISO") + " text and background colors, or convert to CMYK color space for printing. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> replaceAndInvertColor( public ResponseEntity<Resource> replaceAndInvertColor(
@ModelAttribute ReplaceAndInvertColorRequest request) throws IOException { @ModelAttribute ReplaceAndInvertColorRequest request) throws IOException {
InputStreamResource resource = InputStreamResource resource =
@@ -30,11 +30,11 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -564,7 +564,7 @@ public class ScannerEffectController {
summary = "Apply scanner effect to PDF", summary = "Apply scanner effect to PDF",
description = description =
"Applies various effects to simulate a scanned document, including rotation, noise, and edge softening. Input:PDF Output:PDF Type:SISO") "Applies various effects to simulate a scanned document, including rotation, noise, and edge softening. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> scannerEffect( public ResponseEntity<Resource> scannerEffect(
@Valid @ModelAttribute ScannerEffectRequest request) throws IOException { @Valid @ModelAttribute ScannerEffectRequest request) throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
@@ -7,11 +7,11 @@ import java.util.Map;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -39,8 +39,7 @@ public class ShowJavascript {
@Operation( @Operation(
summary = "Grabs all JS from a PDF and returns a single JS file with all code", summary = "Grabs all JS from a PDF and returns a single JS file with all code",
description = "desc. Input:PDF Output:JS Type:SISO") description = "desc. Input:PDF Output:JS Type:SISO")
public ResponseEntity<StreamingResponseBody> extractHeader(@ModelAttribute PDFFile file) public ResponseEntity<Resource> extractHeader(@ModelAttribute PDFFile file) throws Exception {
throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = file.getFileInput();
StringBuilder script = new StringBuilder(); StringBuilder script = new StringBuilder();
boolean foundScript = false; boolean foundScript = false;
@@ -31,13 +31,13 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -93,7 +93,7 @@ public class StampController {
"This endpoint adds a stamp to a given PDF file. Users can specify the stamp" "This endpoint adds a stamp to a given PDF file. Users can specify the stamp"
+ " type (text or image), rotation, opacity, width spacer, and height" + " type (text or image), rotation, opacity, width spacer, and height"
+ " spacer. Input:PDF Output:PDF Type:SISO") + " spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> addStamp(@ModelAttribute AddStampRequest request) public ResponseEntity<Resource> addStamp(@ModelAttribute AddStampRequest request)
throws IOException, Exception { throws IOException, Exception {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pdfFileName = pdfFile.getOriginalFilename(); String pdfFileName = pdfFile.getOriginalFilename();
@@ -10,10 +10,10 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -49,7 +49,7 @@ public class UnlockPDFFormsController {
description = description =
"Removing read-only property from form fields making them fillable" "Removing read-only property from form fields making them fillable"
+ "Input:PDF, Output:PDF. Type:SISO") + "Input:PDF, Output:PDF. Type:SISO")
public ResponseEntity<StreamingResponseBody> unlockPDFForms(@ModelAttribute PDFFile file) { public ResponseEntity<Resource> unlockPDFForms(@ModelAttribute PDFFile file) {
try (PDDocument document = pdfDocumentFactory.load(file)) { try (PDDocument document = pdfDocumentFactory.load(file)) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
@@ -13,7 +13,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -58,8 +57,8 @@ public class PipelineController {
"This endpoint processes multiple PDF files through a configurable pipeline of operations. " "This endpoint processes multiple PDF files through a configurable pipeline of operations. "
+ "Users provide files and a JSON configuration defining the sequence of operations to perform. " + "Users provide files and a JSON configuration defining the sequence of operations to perform. "
+ "Input:PDF Output:PDF/ZIP Type:MIMO") + "Input:PDF Output:PDF/ZIP Type:MIMO")
public ResponseEntity<StreamingResponseBody> handleData( public ResponseEntity<Resource> handleData(@ModelAttribute HandleDataRequest request)
@ModelAttribute HandleDataRequest request) throws DatabindException, JacksonException { throws DatabindException, JacksonException {
MultipartFile[] files = request.getFileInput(); MultipartFile[] files = request.getFileInput();
String jsonString = request.getJson(); String jsonString = request.getJson();
if (files == null) { if (files == null) {
@@ -55,6 +55,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.PKCSException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@@ -64,7 +65,6 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.micrometer.common.util.StringUtils; import io.micrometer.common.util.StringUtils;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -168,8 +168,8 @@ public class CertSignController {
"This endpoint accepts a PDF file, a digital certificate and related" "This endpoint accepts a PDF file, a digital certificate and related"
+ " information to sign the PDF. It then returns the digitally signed PDF" + " information to sign the PDF. It then returns the digitally signed PDF"
+ " file. Input:PDF Output:PDF Type:SISO") + " file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> signPDFWithCert( public ResponseEntity<Resource> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
@ModelAttribute SignPDFWithCertRequest request) throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
String certType = request.getCertType(); String certType = request.getCertType();
MultipartFile privateKeyFile = request.getPrivateKeyFile(); MultipartFile privateKeyFile = request.getPrivateKeyFile();
@@ -5,11 +5,11 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -40,8 +40,8 @@ public class PasswordController {
description = description =
"This endpoint removes the password from a protected PDF file. Users need to" "This endpoint removes the password from a protected PDF file. Users need to"
+ " provide the existing password. Input:PDF Output:PDF Type:SISO") + " provide the existing password. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> removePassword( public ResponseEntity<Resource> removePassword(@ModelAttribute PDFPasswordRequest request)
@ModelAttribute PDFPasswordRequest request) throws IOException { throws IOException {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String password = request.getPassword(); String password = request.getPassword();
@@ -70,8 +70,8 @@ public class PasswordController {
"This endpoint adds password protection to a PDF file. Users can specify a set" "This endpoint adds password protection to a PDF file. Users can specify a set"
+ " of permissions that should be applied to the file. Input:PDF" + " of permissions that should be applied to the file. Input:PDF"
+ " Output:PDF") + " Output:PDF")
public ResponseEntity<StreamingResponseBody> addPassword( public ResponseEntity<Resource> addPassword(@ModelAttribute AddPasswordRequest request)
@ModelAttribute AddPasswordRequest request) throws IOException { throws IOException {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String ownerPassword = request.getOwnerPassword(); String ownerPassword = request.getOwnerPassword();
String password = request.getPassword(); String password = request.getPassword();
@@ -33,13 +33,13 @@ import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -108,8 +108,8 @@ public class RedactController {
"This endpoint redacts content from a PDF file based on manually specified areas. " "This endpoint redacts content from a PDF file based on manually specified areas. "
+ "Users can specify areas to redact and optionally convert the PDF to an image. " + "Users can specify areas to redact and optionally convert the PDF to an image. "
+ "Input:PDF Output:PDF Type:SISO") + "Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> redactPDF( public ResponseEntity<Resource> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
@ModelAttribute ManualRedactPdfRequest request) throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
List<RedactionArea> redactionAreas = request.getRedactions(); List<RedactionArea> redactionAreas = request.getRedactions();
@@ -501,8 +501,7 @@ public class RedactController {
"This endpoint automatically redacts text from a PDF file based on specified patterns. " "This endpoint automatically redacts text from a PDF file based on specified patterns. "
+ "Users can provide text patterns to redact, with options for regex and whole word matching. " + "Users can provide text patterns to redact, with options for regex and whole word matching. "
+ "Input:PDF Output:PDF Type:SISO") + "Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> redactPdf( public ResponseEntity<Resource> redactPdf(@ModelAttribute RedactPdfRequest request) {
@ModelAttribute RedactPdfRequest request) {
String[] listOfText = request.getListOfText().split("\n"); String[] listOfText = request.getListOfText().split("\n");
boolean useRegex = Boolean.TRUE.equals(request.getUseRegex()); boolean useRegex = Boolean.TRUE.equals(request.getUseRegex());
boolean wholeWordSearchBool = Boolean.TRUE.equals(request.getWholeWordSearch()); boolean wholeWordSearchBool = Boolean.TRUE.equals(request.getWholeWordSearch());
@@ -7,11 +7,11 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -40,7 +40,7 @@ public class RemoveCertSignController {
description = description =
"This endpoint accepts a PDF file and returns the PDF file without the digital" "This endpoint accepts a PDF file and returns the PDF file without the digital"
+ " signature. Input:PDF, Output:PDF Type:SISO") + " signature. Input:PDF, Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> removeCertSignPDF(@ModelAttribute PDFFile request) public ResponseEntity<Resource> removeCertSignPDF(@ModelAttribute PDFFile request)
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
@@ -24,11 +24,11 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -59,8 +59,8 @@ public class SanitizeController {
description = description =
"This endpoint processes a PDF file and removes specific elements based on the" "This endpoint processes a PDF file and removes specific elements based on the"
+ " provided options. Input:PDF Output:PDF Type:SISO") + " provided options. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> sanitizePDF( public ResponseEntity<Resource> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
@ModelAttribute SanitizePdfRequest request) throws IOException { throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
boolean removeJavaScript = Boolean.TRUE.equals(request.getRemoveJavaScript()); boolean removeJavaScript = Boolean.TRUE.equals(request.getRemoveJavaScript());
boolean removeEmbeddedFiles = Boolean.TRUE.equals(request.getRemoveEmbeddedFiles()); boolean removeEmbeddedFiles = Boolean.TRUE.equals(request.getRemoveEmbeddedFiles());
@@ -28,11 +28,11 @@ import org.bouncycastle.tsp.TimeStampRequest;
import org.bouncycastle.tsp.TimeStampRequestGenerator; import org.bouncycastle.tsp.TimeStampRequestGenerator;
import org.bouncycastle.tsp.TimeStampResponse; import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.tsp.TimeStampToken; import org.bouncycastle.tsp.TimeStampToken;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -87,8 +87,8 @@ public class TimestampController {
+ " document timestamp into the PDF. Only a SHA-256 hash of the" + " document timestamp into the PDF. Only a SHA-256 hash of the"
+ " document is sent to the TSA — the PDF itself never leaves the" + " document is sent to the TSA — the PDF itself never leaves the"
+ " server. Input:PDF Output:PDF Type:SISO") + " server. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> timestampPdf( public ResponseEntity<Resource> timestampPdf(@ModelAttribute TimestampPdfRequest request)
@ModelAttribute TimestampPdfRequest request) throws Exception { throws Exception {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
ApplicationProperties.Security.Timestamp tsConfig = ApplicationProperties.Security.Timestamp tsConfig =
applicationProperties.getSecurity().getTimestamp(); applicationProperties.getSecurity().getTimestamp();
@@ -24,13 +24,13 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -76,8 +76,8 @@ public class WatermarkController {
"This endpoint adds a watermark to a given PDF file. Users can specify the" "This endpoint adds a watermark to a given PDF file. Users can specify the"
+ " watermark type (text or image), rotation, opacity, width spacer, and" + " watermark type (text or image), rotation, opacity, width spacer, and"
+ " height spacer. Input:PDF Output:PDF Type:SISO") + " height spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<StreamingResponseBody> addWatermark( public ResponseEntity<Resource> addWatermark(@Valid @ModelAttribute AddWatermarkRequest request)
@Valid @ModelAttribute AddWatermarkRequest request) throws IOException, Exception { throws IOException, Exception {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pdfFileName = pdfFile.getOriginalFilename(); String pdfFileName = pdfFile.getOriginalFilename();
if (pdfFileName != null && (pdfFileName.contains("..") || pdfFileName.startsWith("/"))) { if (pdfFileName != null && (pdfFileName.contains("..") || pdfFileName.startsWith("/"))) {
@@ -1,10 +1,10 @@
package stirling.software.SPDF.model.api.converters; package stirling.software.SPDF.model.api.converters;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -29,7 +29,7 @@ public class ConvertPDFToMarkdown {
summary = "Convert PDF to Markdown", summary = "Convert PDF to Markdown",
description = description =
"This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO") "This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO")
public ResponseEntity<StreamingResponseBody> processPdfToMarkdown(@ModelAttribute PDFFile file) public ResponseEntity<Resource> processPdfToMarkdown(@ModelAttribute PDFFile file)
throws Exception { throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = file.getFileInput();
PDFToFile pdfToFile = new PDFToFile(tempFileManager); PDFToFile pdfToFile = new PDFToFile(tempFileManager);
@@ -248,24 +248,28 @@ public class PdfJsonConversionService {
} }
} }
public byte[] convertPdfToJson(MultipartFile file) throws IOException { public void convertPdfToJson(MultipartFile file, OutputStream out) throws IOException {
return convertPdfToJson(file, null, false); convertPdfToJson(file, null, false, out);
} }
public byte[] convertPdfToJson(MultipartFile file, boolean lightweight) throws IOException { public void convertPdfToJson(MultipartFile file, boolean lightweight, OutputStream out)
return convertPdfToJson(file, null, lightweight);
}
public byte[] convertPdfToJson(
MultipartFile file, Consumer<PdfJsonConversionProgress> progressCallback)
throws IOException { throws IOException {
return convertPdfToJson(file, progressCallback, false); convertPdfToJson(file, null, lightweight, out);
} }
public byte[] convertPdfToJson( public void convertPdfToJson(
MultipartFile file, MultipartFile file,
Consumer<PdfJsonConversionProgress> progressCallback, Consumer<PdfJsonConversionProgress> progressCallback,
boolean lightweight) OutputStream out)
throws IOException {
convertPdfToJson(file, progressCallback, false, out);
}
public void convertPdfToJson(
MultipartFile file,
Consumer<PdfJsonConversionProgress> progressCallback,
boolean lightweight,
OutputStream out)
throws IOException { throws IOException {
if (file == null) { if (file == null) {
throw ExceptionUtils.createNullArgumentException("fileInput"); throw ExceptionUtils.createNullArgumentException("fileInput");
@@ -593,7 +597,9 @@ public class PdfJsonConversionService {
pdfJson.getPages().size()); pdfJson.getPages().size());
} }
byte[] result = objectMapper.writeValueAsBytes(pdfJson); // Stream JSON directly to the caller's OutputStream to avoid allocating the
// entire document on the heap before writing it out.
objectMapper.writeValue(out, pdfJson);
progress.accept(PdfJsonConversionProgress.complete()); progress.accept(PdfJsonConversionProgress.complete());
// Clear Type3 cache entries immediately for non-cached conversions // Clear Type3 cache entries immediately for non-cached conversions
@@ -602,15 +608,13 @@ public class PdfJsonConversionService {
if (!useLazyImages) { if (!useLazyImages) {
clearType3CacheEntriesForJob(jobId); clearType3CacheEntriesForJob(jobId);
} }
return result;
} }
} finally { } finally {
closeQuietly(normalizedFile); closeQuietly(normalizedFile);
} }
} }
public byte[] convertJsonToPdf(MultipartFile file) throws IOException { public void convertJsonToPdf(MultipartFile file, OutputStream out) throws IOException {
if (file == null) { if (file == null) {
throw ExceptionUtils.createNullArgumentException("fileInput"); throw ExceptionUtils.createNullArgumentException("fileInput");
} }
@@ -804,15 +808,11 @@ public class PdfJsonConversionService {
log.info("JSON->PDF conversion complete: {} pages", pages.size()); log.info("JSON->PDF conversion complete: {} pages", pages.size());
} }
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { // Stream the PDF directly to the caller's OutputStream PDFBox writes incrementally.
document.save(baos); document.save(out);
byte[] result = baos.toByteArray();
// Clear Type3 cache entries for this conversion // Clear Type3 cache entries for this conversion
clearType3CacheEntriesForJob(syntheticJobId); clearType3CacheEntriesForJob(syntheticJobId);
return result;
}
} }
} }
@@ -6075,7 +6075,8 @@ public class PdfJsonConversionService {
* Extracts document metadata, fonts, and page dimensions without page content. Caches the PDF * Extracts document metadata, fonts, and page dimensions without page content. Caches the PDF
* bytes for subsequent page requests. * bytes for subsequent page requests.
*/ */
public byte[] extractDocumentMetadata(MultipartFile file, String jobId) throws IOException { public void extractDocumentMetadata(MultipartFile file, String jobId, OutputStream out)
throws IOException {
if (file == null) { if (file == null) {
throw ExceptionUtils.createNullArgumentException("fileInput"); throw ExceptionUtils.createNullArgumentException("fileInput");
} }
@@ -6170,12 +6171,13 @@ public class PdfJsonConversionService {
progress.accept( progress.accept(
PdfJsonConversionProgress.of(100, "complete", "Metadata extraction complete")); PdfJsonConversionProgress.of(100, "complete", "Metadata extraction complete"));
return objectMapper.writeValueAsBytes(docMetadata); objectMapper.writeValue(out, docMetadata);
} }
} }
/** Extracts a single page from cached PDF bytes. Re-loads the PDF for each request. */ /** Extracts a single page from cached PDF bytes. Re-loads the PDF for each request. */
public byte[] extractSinglePage(String jobId, int pageNumber) throws IOException { public void extractSinglePage(String jobId, int pageNumber, OutputStream out)
throws IOException {
CachedPdfDocument cached = getCachedDocument(jobId); CachedPdfDocument cached = getCachedDocument(jobId);
if (cached == null) { if (cached == null) {
throw new stirling.software.SPDF.exception.CacheUnavailableException( throw new stirling.software.SPDF.exception.CacheUnavailableException(
@@ -6326,11 +6328,12 @@ public class PdfJsonConversionService {
pageModel.getAnnotations().size(), pageModel.getAnnotations().size(),
jobId); jobId);
return objectMapper.writeValueAsBytes(pageModel); objectMapper.writeValue(out, pageModel);
} }
} }
public byte[] extractPageFonts(String jobId, int pageNumber) throws IOException { public void extractPageFonts(String jobId, int pageNumber, OutputStream out)
throws IOException {
CachedPdfDocument cached = getCachedDocument(jobId); CachedPdfDocument cached = getCachedDocument(jobId);
if (cached == null) { if (cached == null) {
throw new stirling.software.SPDF.exception.CacheUnavailableException( throw new stirling.software.SPDF.exception.CacheUnavailableException(
@@ -6347,7 +6350,8 @@ public class PdfJsonConversionService {
Map<PDFont, String> pageMap = Map<PDFont, String> pageMap =
pageFontResources != null ? pageFontResources.get(pageNumber) : null; pageFontResources != null ? pageFontResources.get(pageNumber) : null;
if (pageMap == null || pageMap.isEmpty()) { if (pageMap == null || pageMap.isEmpty()) {
return objectMapper.writeValueAsBytes(Collections.emptyList()); objectMapper.writeValue(out, Collections.emptyList());
return;
} }
Map<String, PdfJsonFont> cachedFonts = cached.getFonts(); Map<String, PdfJsonFont> cachedFonts = cached.getFonts();
@@ -6375,7 +6379,7 @@ public class PdfJsonConversionService {
pageFonts.sort( pageFonts.sort(
Comparator.comparing( Comparator.comparing(
PdfJsonFont::getUid, Comparator.nullsLast(Comparator.naturalOrder()))); PdfJsonFont::getUid, Comparator.nullsLast(Comparator.naturalOrder())));
return objectMapper.writeValueAsBytes(pageFonts); objectMapper.writeValue(out, pageFonts);
} }
public byte[] exportUpdatedPages(String jobId, PdfJsonDocument updates) throws IOException { public byte[] exportUpdatedPages(String jobId, PdfJsonDocument updates) throws IOException {
@@ -1,6 +1,7 @@
package stirling.software.SPDF.service.pdfjson; package stirling.software.SPDF.service.pdfjson;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@@ -81,14 +82,15 @@ public class PdfLazyLoadingService {
* @param jobId The job ID for caching * @param jobId The job ID for caching
* @param fonts Font map (will be populated) * @param fonts Font map (will be populated)
* @param pageFontResources Page font resources map (will be populated) * @param pageFontResources Page font resources map (will be populated)
* @return Serialized metadata JSON * @param out The output stream to write the serialized metadata JSON to
* @throws IOException If extraction fails * @throws IOException If extraction fails
*/ */
public byte[] extractDocumentMetadata( public void extractDocumentMetadata(
MultipartFile file, MultipartFile file,
String jobId, String jobId,
Map<String, PdfJsonFont> fonts, Map<String, PdfJsonFont> fonts,
Map<Integer, Map<PDFont, String>> pageFontResources) Map<Integer, Map<PDFont, String>> pageFontResources,
OutputStream out)
throws IOException { throws IOException {
if (file == null) { if (file == null) {
throw ExceptionUtils.createNullArgumentException("fileInput"); throw ExceptionUtils.createNullArgumentException("fileInput");
@@ -160,7 +162,7 @@ public class PdfLazyLoadingService {
progress.accept( progress.accept(
PdfJsonConversionProgress.of(100, "complete", "Metadata extraction complete")); PdfJsonConversionProgress.of(100, "complete", "Metadata extraction complete"));
return objectMapper.writeValueAsBytes(docMetadata); objectMapper.writeValue(out, docMetadata);
} }
} }
@@ -174,10 +176,10 @@ public class PdfLazyLoadingService {
* @param filterImageXObjectsFromResources Function to filter image XObjects * @param filterImageXObjectsFromResources Function to filter image XObjects
* @param extractText Function to extract text elements for the page * @param extractText Function to extract text elements for the page
* @param extractAnnotations Function to extract annotations for the page * @param extractAnnotations Function to extract annotations for the page
* @return Serialized page JSON * @param out The output stream to write the serialized page JSON to
* @throws IOException If extraction fails * @throws IOException If extraction fails
*/ */
public byte[] extractSinglePage( public void extractSinglePage(
String jobId, String jobId,
int pageNumber, int pageNumber,
java.util.function.Function<COSBase, PdfJsonCosValue> serializeCosValue, java.util.function.Function<COSBase, PdfJsonCosValue> serializeCosValue,
@@ -186,7 +188,8 @@ public class PdfLazyLoadingService {
java.util.function.BiFunction<PDDocument, Integer, List<PdfJsonTextElement>> java.util.function.BiFunction<PDDocument, Integer, List<PdfJsonTextElement>>
extractText, extractText,
java.util.function.BiFunction<PDDocument, Integer, List<PdfJsonAnnotation>> java.util.function.BiFunction<PDDocument, Integer, List<PdfJsonAnnotation>>
extractAnnotations) extractAnnotations,
OutputStream out)
throws IOException { throws IOException {
CachedPdfDocument cached = documentCache.get(jobId); CachedPdfDocument cached = documentCache.get(jobId);
if (cached == null) { if (cached == null) {
@@ -238,7 +241,7 @@ public class PdfLazyLoadingService {
pageModel.getAnnotations().size(), pageModel.getAnnotations().size(),
jobId); jobId);
return objectMapper.writeValueAsBytes(pageModel); objectMapper.writeValue(out, pageModel);
} }
} }
@@ -20,11 +20,12 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.general.BookletImpositionRequest; import stirling.software.SPDF.model.api.general.BookletImpositionRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -33,14 +34,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class BookletImpositionControllerTest { class BookletImpositionControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -94,8 +96,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -126,8 +127,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -144,8 +144,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -162,8 +161,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -180,8 +178,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -198,8 +195,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -216,8 +212,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -232,8 +227,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -261,8 +255,7 @@ class BookletImpositionControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.createBookletImposition(request);
controller.createBookletImposition(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -27,11 +27,12 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.general.CropPdfForm; import stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -41,14 +42,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@DisplayName("CropController Tests") @DisplayName("CropController Tests")
class CropControllerTest { class CropControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -205,7 +207,7 @@ class CropControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
.thenReturn(newDocument); .thenReturn(newDocument);
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request); ResponseEntity<Resource> response = cropController.cropPdf(request);
assertThat(response) assertThat(response)
.isNotNull() .isNotNull()
@@ -242,7 +244,7 @@ class CropControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
.thenReturn(newDocument); .thenReturn(newDocument);
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request); ResponseEntity<Resource> response = cropController.cropPdf(request);
assertThat(response).isNotNull(); assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -282,7 +284,7 @@ class CropControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc))
.thenReturn(newDoc); .thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request); ResponseEntity<Resource> response = cropController.cropPdf(request);
assertThat(response).isNotNull(); assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -313,7 +315,7 @@ class CropControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc))
.thenReturn(newDoc); .thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request); ResponseEntity<Resource> response = cropController.cropPdf(request);
assertThat(response).isNotNull(); assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -676,7 +678,7 @@ class CropControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
.thenReturn(newDocument); .thenReturn(newDocument);
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request); ResponseEntity<Resource> response = cropController.cropPdf(request);
assertThat(response).isNotNull(); assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -704,7 +706,7 @@ class CropControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
.thenReturn(newDocument); .thenReturn(newDocument);
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request); ResponseEntity<Resource> response = cropController.cropPdf(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
verify(mockDocument, times(1)).close(); verify(mockDocument, times(1)).close();
@@ -25,10 +25,10 @@ import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.controller.api.EditTableOfContentsController.BookmarkItem; import stirling.software.SPDF.controller.api.EditTableOfContentsController.BookmarkItem;
import stirling.software.SPDF.model.api.EditTableOfContentsRequest; import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
@@ -255,7 +255,7 @@ class EditTableOfContentsControllerTest {
.save(any(File.class)); .save(any(File.class));
// When // When
ResponseEntity<StreamingResponseBody> result = ResponseEntity<Resource> result =
editTableOfContentsController.editTableOfContents(request); editTableOfContentsController.editTableOfContents(request);
// Then // Then
@@ -319,7 +319,7 @@ class EditTableOfContentsControllerTest {
.save(any(File.class)); .save(any(File.class));
// When // When
ResponseEntity<StreamingResponseBody> result = ResponseEntity<Resource> result =
editTableOfContentsController.editTableOfContents(request); editTableOfContentsController.editTableOfContents(request);
// Then // Then
@@ -373,7 +373,7 @@ class EditTableOfContentsControllerTest {
.save(any(File.class)); .save(any(File.class));
// When // When
ResponseEntity<StreamingResponseBody> result = ResponseEntity<Resource> result =
editTableOfContentsController.editTableOfContents(request); editTableOfContentsController.editTableOfContents(request);
// Then // Then
@@ -18,11 +18,12 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest; import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -31,14 +32,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class MultiPageLayoutControllerTest { class MultiPageLayoutControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -97,7 +99,7 @@ class MultiPageLayoutControllerTest {
req.setAddBorder(Boolean.FALSE); req.setAddBorder(Boolean.FALSE);
req.setFileInput(fileWithExt); req.setFileInput(fileWithExt);
ResponseEntity<StreamingResponseBody> resp = controller.mergeMultiplePagesIntoOne(req); ResponseEntity<Resource> resp = controller.mergeMultiplePagesIntoOne(req);
Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode()); Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode());
Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType()); Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType());
Assertions.assertNotNull(resp.getBody()); Assertions.assertNotNull(resp.getBody());
@@ -122,7 +124,7 @@ class MultiPageLayoutControllerTest {
req.setAddBorder(Boolean.TRUE); req.setAddBorder(Boolean.TRUE);
req.setFileInput(fileWithExt); req.setFileInput(fileWithExt);
ResponseEntity<StreamingResponseBody> resp = controller.mergeMultiplePagesIntoOne(req); ResponseEntity<Resource> resp = controller.mergeMultiplePagesIntoOne(req);
Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode()); Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode());
Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType()); Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType());
Assertions.assertNotNull(resp.getBody()); Assertions.assertNotNull(resp.getBody());
@@ -145,7 +147,7 @@ class MultiPageLayoutControllerTest {
req.setAddBorder(Boolean.TRUE); req.setAddBorder(Boolean.TRUE);
req.setFileInput(fileNoExt); req.setFileInput(fileNoExt);
ResponseEntity<StreamingResponseBody> resp = controller.mergeMultiplePagesIntoOne(req); ResponseEntity<Resource> resp = controller.mergeMultiplePagesIntoOne(req);
Assertions.assertEquals( Assertions.assertEquals(
"name_multi_page_layout.pdf", "name_multi_page_layout.pdf",
resp.getHeaders().getContentDisposition().getFilename()); resp.getHeaders().getContentDisposition().getFilename());
@@ -25,12 +25,13 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest; import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -39,14 +40,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PdfOverlayControllerTest { class PdfOverlayControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -107,7 +109,7 @@ class PdfOverlayControllerTest {
when(pdfDocumentFactory.load(any(MultipartFile.class))) when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request); ResponseEntity<Resource> response = controller.overlayPdfs(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -141,7 +143,7 @@ class PdfOverlayControllerTest {
when(pdfDocumentFactory.load(any(MultipartFile.class))) when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request); ResponseEntity<Resource> response = controller.overlayPdfs(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -173,7 +175,7 @@ class PdfOverlayControllerTest {
when(pdfDocumentFactory.load(any(MultipartFile.class))) when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request); ResponseEntity<Resource> response = controller.overlayPdfs(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -204,7 +206,7 @@ class PdfOverlayControllerTest {
when(pdfDocumentFactory.load(any(MultipartFile.class))) when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request); ResponseEntity<Resource> response = controller.overlayPdfs(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -294,7 +296,7 @@ class PdfOverlayControllerTest {
when(pdfDocumentFactory.load(any(MultipartFile.class))) when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request); ResponseEntity<Resource> response = controller.overlayPdfs(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -327,7 +329,7 @@ class PdfOverlayControllerTest {
when(pdfDocumentFactory.load(any(MultipartFile.class))) when(pdfDocumentFactory.load(any(MultipartFile.class)))
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request); ResponseEntity<Resource> response = controller.overlayPdfs(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -16,10 +16,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest; import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
@@ -67,7 +67,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(mockDoc); when(pdfDocumentFactory.load(file)).thenReturn(mockDoc);
when(mockDoc.getNumberOfPages()).thenReturn(5); when(mockDoc.getNumberOfPages()).thenReturn(5);
ResponseEntity<StreamingResponseBody> response = controller.deletePages(request); ResponseEntity<Resource> response = controller.deletePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -97,7 +97,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -127,7 +127,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
verify(mockNewDoc).addPage(page1); verify(mockNewDoc).addPage(page1);
@@ -155,7 +155,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
verify(mockNewDoc).addPage(page0); verify(mockNewDoc).addPage(page0);
@@ -181,7 +181,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -208,7 +208,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -232,7 +232,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -256,7 +256,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -284,7 +284,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -308,7 +308,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
// 2 pages * 3 duplicates = 6 addPage calls // 2 pages * 3 duplicates = 6 addPage calls
@@ -333,7 +333,7 @@ class RearrangePagesPDFControllerTest {
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
.thenReturn(mockNewDoc); .thenReturn(mockNewDoc);
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request); ResponseEntity<Resource> response = controller.rearrangePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -20,10 +20,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.general.RotatePDFRequest; import stirling.software.SPDF.model.api.general.RotatePDFRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -75,7 +75,7 @@ public class RotationControllerTest {
when(mockPage.getRotation()).thenReturn(0); when(mockPage.getRotation()).thenReturn(0);
// Act // Act
ResponseEntity<StreamingResponseBody> response = rotationController.rotatePDF(request); ResponseEntity<Resource> response = rotationController.rotatePDF(request);
// Assert // Assert
verify(mockPage).setRotation(90); verify(mockPage).setRotation(90);
@@ -20,11 +20,12 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.general.ScalePagesRequest; import stirling.software.SPDF.model.api.general.ScalePagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -33,14 +34,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ScalePagesControllerTest { class ScalePagesControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -101,7 +103,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -123,7 +125,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -143,7 +145,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -163,7 +165,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -183,7 +185,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -220,7 +222,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -240,7 +242,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -281,7 +283,7 @@ class ScalePagesControllerTest {
setupFactory(); setupFactory();
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request); ResponseEntity<Resource> response = controller.scalePages(request);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -31,9 +31,10 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.model.api.converters.ConvertEbookToPdfRequest; import stirling.software.SPDF.model.api.converters.ConvertEbookToPdfRequest;
@@ -48,14 +49,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertEbookToPDFControllerTest { class ConvertEbookToPDFControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -145,14 +147,13 @@ class ConvertEbookToPDFControllerTest {
return execResult; return execResult;
}); });
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("result".getBytes());
streamingOk("result".getBytes());
wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString())) wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
gu.when(() -> GeneralUtils.generateFilename("ebook.epub", "_convertedToPDF.pdf")) gu.when(() -> GeneralUtils.generateFilename("ebook.epub", "_convertedToPDF.pdf"))
.thenReturn("ebook_convertedToPDF.pdf"); .thenReturn("ebook_convertedToPDF.pdf");
ResponseEntity<StreamingResponseBody> response = controller.convertEbookToPdf(request); ResponseEntity<Resource> response = controller.convertEbookToPdf(request);
assertSame(expectedResponse, response); assertSame(expectedResponse, response);
@@ -262,11 +263,11 @@ class ConvertEbookToPDFControllerTest {
gu.when(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class))) gu.when(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class)))
.thenReturn(optimizedBytes); .thenReturn(optimizedBytes);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(optimizedBytes); ResponseEntity<Resource> expectedResponse = streamingOk(optimizedBytes);
wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString())) wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.convertEbookToPdf(request); ResponseEntity<Resource> response = controller.convertEbookToPdf(request);
assertSame(expectedResponse, response); assertSame(expectedResponse, response);
gu.verify(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class))); gu.verify(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class)));
@@ -23,11 +23,12 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.converters.EmlToPdfRequest; import stirling.software.common.model.api.converters.EmlToPdfRequest;
@@ -40,14 +41,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertEmlToPDFTest { class ConvertEmlToPDFTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -82,7 +84,7 @@ class ConvertEmlToPDFTest {
EmlToPdfRequest request = new EmlToPdfRequest(); EmlToPdfRequest request = new EmlToPdfRequest();
request.setFileInput(emptyFile); request.setFileInput(emptyFile);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue( assertTrue(
@@ -98,7 +100,7 @@ class ConvertEmlToPDFTest {
EmlToPdfRequest request = new EmlToPdfRequest(); EmlToPdfRequest request = new EmlToPdfRequest();
request.setFileInput(file); request.setFileInput(file);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue( assertTrue(
@@ -113,7 +115,7 @@ class ConvertEmlToPDFTest {
EmlToPdfRequest request = new EmlToPdfRequest(); EmlToPdfRequest request = new EmlToPdfRequest();
request.setFileInput(file); request.setFileInput(file);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
} }
@@ -126,7 +128,7 @@ class ConvertEmlToPDFTest {
EmlToPdfRequest request = new EmlToPdfRequest(); EmlToPdfRequest request = new EmlToPdfRequest();
request.setFileInput(file); request.setFileInput(file);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue( assertTrue(
@@ -146,7 +148,7 @@ class ConvertEmlToPDFTest {
when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint"); when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint");
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(pdfBytes); ResponseEntity<Resource> expectedResponse = streamingOk(pdfBytes);
try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class); try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class);
MockedStatic<WebResponseUtils> wrMock = MockedStatic<WebResponseUtils> wrMock =
@@ -170,7 +172,7 @@ class ConvertEmlToPDFTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertArrayEquals(pdfBytes, drainBody(response)); assertArrayEquals(pdfBytes, drainBody(response));
@@ -188,7 +190,7 @@ class ConvertEmlToPDFTest {
request.setFileInput(file); request.setFileInput(file);
request.setDownloadHtml(true); request.setDownloadHtml(true);
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse =
streamingOk(htmlContent.getBytes(StandardCharsets.UTF_8)); streamingOk(htmlContent.getBytes(StandardCharsets.UTF_8));
try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class); try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class);
@@ -209,7 +211,7 @@ class ConvertEmlToPDFTest {
any(TempFile.class), anyString(), any(MediaType.class))) any(TempFile.class), anyString(), any(MediaType.class)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -235,7 +237,7 @@ class ConvertEmlToPDFTest {
eq(customHtmlSanitizer))) eq(customHtmlSanitizer)))
.thenThrow(new IOException("Parse error")); .thenThrow(new IOException("Parse error"));
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertTrue( assertTrue(
@@ -263,7 +265,7 @@ class ConvertEmlToPDFTest {
any(), any(), any(), any(), any(), any(), any())) any(), any(), any(), any(), any(), any(), any()))
.thenReturn(null); .thenReturn(null);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertTrue( assertTrue(
@@ -287,7 +289,7 @@ class ConvertEmlToPDFTest {
when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint"); when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint");
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(pdfBytes); ResponseEntity<Resource> expectedResponse = streamingOk(pdfBytes);
try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class); try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class);
MockedStatic<WebResponseUtils> wrMock = MockedStatic<WebResponseUtils> wrMock =
@@ -305,7 +307,7 @@ class ConvertEmlToPDFTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -330,7 +332,7 @@ class ConvertEmlToPDFTest {
any(), any(), any(), any(), any(), any(), any())) any(), any(), any(), any(), any(), any(), any()))
.thenThrow(new InterruptedException("interrupted")); .thenThrow(new InterruptedException("interrupted"));
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request); ResponseEntity<Resource> response = controller.convertEmlToPdf(request);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertTrue( assertTrue(
@@ -20,10 +20,11 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.converters.HTMLToPdfRequest; import stirling.software.common.model.api.converters.HTMLToPdfRequest;
@@ -37,14 +38,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertHtmlToPDFTest { class ConvertHtmlToPDFTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -104,7 +106,7 @@ class ConvertHtmlToPDFTest {
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes)) when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes))
.thenReturn(processedPdf); .thenReturn(processedPdf);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf); ResponseEntity<Resource> expectedResponse = streamingOk(processedPdf);
try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class); try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class);
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class); MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
@@ -131,7 +133,7 @@ class ConvertHtmlToPDFTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.HtmlToPdf(request); ResponseEntity<Resource> response = controller.HtmlToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -152,7 +154,7 @@ class ConvertHtmlToPDFTest {
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes)) when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes))
.thenReturn(processedPdf); .thenReturn(processedPdf);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf); ResponseEntity<Resource> expectedResponse = streamingOk(processedPdf);
try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class); try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class);
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class); MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
@@ -179,7 +181,7 @@ class ConvertHtmlToPDFTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.HtmlToPdf(request); ResponseEntity<Resource> response = controller.HtmlToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -12,9 +12,10 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
@@ -26,14 +27,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertImgPDFControllerTest { class ConvertImgPDFControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -22,10 +22,11 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.GeneralFile; import stirling.software.common.model.api.GeneralFile;
@@ -39,14 +40,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertMarkdownToPdfTest { class ConvertMarkdownToPdfTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -106,7 +108,7 @@ class ConvertMarkdownToPdfTest {
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(any(byte[].class))) when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(any(byte[].class)))
.thenReturn(processedPdf); .thenReturn(processedPdf);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf); ResponseEntity<Resource> expectedResponse = streamingOk(processedPdf);
try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class); try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class);
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class); MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
@@ -133,7 +135,7 @@ class ConvertMarkdownToPdfTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.markdownToPdf(generalFile); ResponseEntity<Resource> response = controller.markdownToPdf(generalFile);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -30,10 +30,11 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.model.api.converters.ConvertPdfToEpubRequest; import stirling.software.SPDF.model.api.converters.ConvertPdfToEpubRequest;
@@ -48,14 +49,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertPDFToEpubControllerTest { class ConvertPDFToEpubControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -142,7 +144,7 @@ class ConvertPDFToEpubControllerTest {
gu.when(() -> GeneralUtils.generateFilename("novel.pdf", "_convertedToEPUB.epub")) gu.when(() -> GeneralUtils.generateFilename("novel.pdf", "_convertedToEPUB.epub"))
.thenReturn("novel_convertedToEPUB.epub"); .thenReturn("novel_convertedToEPUB.epub");
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToEpub(request); ResponseEntity<Resource> response = controller.convertPdfToEpub(request);
List<String> command = commandCaptor.getValue(); List<String> command = commandCaptor.getValue();
assertEquals(13, command.size()); assertEquals(13, command.size());
@@ -234,7 +236,7 @@ class ConvertPDFToEpubControllerTest {
gu.when(() -> GeneralUtils.generateFilename("story.pdf", "_convertedToEPUB.epub")) gu.when(() -> GeneralUtils.generateFilename("story.pdf", "_convertedToEPUB.epub"))
.thenReturn("story_convertedToEPUB.epub"); .thenReturn("story_convertedToEPUB.epub");
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToEpub(request); ResponseEntity<Resource> response = controller.convertPdfToEpub(request);
List<String> command = commandCaptor.getValue(); List<String> command = commandCaptor.getValue();
assertTrue(command.stream().noneMatch(arg -> "--chapter".equals(arg))); assertTrue(command.stream().noneMatch(arg -> "--chapter".equals(arg)));
@@ -319,7 +321,7 @@ class ConvertPDFToEpubControllerTest {
gu.when(() -> GeneralUtils.generateFilename("book.pdf", "_convertedToAZW3.azw3")) gu.when(() -> GeneralUtils.generateFilename("book.pdf", "_convertedToAZW3.azw3"))
.thenReturn("book_convertedToAZW3.azw3"); .thenReturn("book_convertedToAZW3.azw3");
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToEpub(request); ResponseEntity<Resource> response = controller.convertPdfToEpub(request);
List<String> command = commandCaptor.getValue(); List<String> command = commandCaptor.getValue();
assertEquals("ebook-convert", command.get(0)); assertEquals("ebook-convert", command.get(0));
@@ -20,10 +20,10 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -81,7 +81,7 @@ class ConvertPDFToExcelControllerTest {
Mockito.eq(true))) Mockito.eq(true)))
.thenReturn(List.of(1)); .thenReturn(List.of(1));
ResponseEntity<StreamingResponseBody> response = controller.pdfToExcel(request); ResponseEntity<Resource> response = controller.pdfToExcel(request);
// tabula may or may not find tables in an empty page // tabula may or may not find tables in an empty page
assertNotNull(response); assertNotNull(response);
@@ -21,10 +21,11 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest; import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest; import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
@@ -40,14 +41,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertPDFToOfficeTest { class ConvertPDFToOfficeTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -85,8 +87,7 @@ class ConvertPDFToOfficeTest {
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
request.setOutputFormat("pptx"); request.setOutputFormat("pptx");
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("pptx-content".getBytes());
streamingOk("pptx-content".getBytes());
try (MockedStatic<PDFToFile> mock = try (MockedStatic<PDFToFile> mock =
Mockito.mockStatic(PDFToFile.class, Mockito.CALLS_REAL_METHODS)) { Mockito.mockStatic(PDFToFile.class, Mockito.CALLS_REAL_METHODS)) {
@@ -115,8 +116,7 @@ class ConvertPDFToOfficeTest {
realDoc.addPage(new org.apache.pdfbox.pdmodel.PDPage()); realDoc.addPage(new org.apache.pdfbox.pdmodel.PDPage());
when(pdfDocumentFactory.load(pdfFile)).thenReturn(realDoc); when(pdfDocumentFactory.load(pdfFile)).thenReturn(realDoc);
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("text content".getBytes());
streamingOk("text content".getBytes());
try (MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class); try (MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
MockedStatic<WebResponseUtils> wrMock = MockedStatic<WebResponseUtils> wrMock =
@@ -131,8 +131,7 @@ class ConvertPDFToOfficeTest {
any(TempFile.class), anyString(), any(MediaType.class))) any(TempFile.class), anyString(), any(MediaType.class)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.processPdfToRTForTXT(request);
controller.processPdfToRTForTXT(request);
assertSame(expectedResponse, response); assertSame(expectedResponse, response);
} }
@@ -4,16 +4,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -22,11 +24,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.service.PdfJsonConversionService; import stirling.software.SPDF.service.PdfJsonConversionService;
import stirling.software.common.model.api.GeneralFile; import stirling.software.common.model.api.GeneralFile;
@@ -58,10 +60,11 @@ class ConvertPdfJsonControllerTest {
}); });
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws IOException {
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream in = response.getBody().getInputStream()) {
in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -82,10 +85,17 @@ class ConvertPdfJsonControllerTest {
PDFFile request = new PDFFile(); PDFFile request = new PDFFile();
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
when(pdfJsonConversionService.convertPdfToJson(pdfFile, false)).thenReturn(jsonBytes); // Service writes directly to the OutputStream passed by the controller
doAnswer(
inv -> {
OutputStream os = inv.getArgument(2, OutputStream.class);
os.write(jsonBytes);
return null;
})
.when(pdfJsonConversionService)
.convertPdfToJson(eq(pdfFile), eq(false), any(OutputStream.class));
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.convertPdfToJson(request, false);
controller.convertPdfToJson(request, false);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
@@ -100,12 +110,20 @@ class ConvertPdfJsonControllerTest {
PDFFile request = new PDFFile(); PDFFile request = new PDFFile();
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
when(pdfJsonConversionService.convertPdfToJson(pdfFile, true)).thenReturn(jsonBytes); doAnswer(
inv -> {
OutputStream os = inv.getArgument(2, OutputStream.class);
os.write(jsonBytes);
return null;
})
.when(pdfJsonConversionService)
.convertPdfToJson(eq(pdfFile), eq(true), any(OutputStream.class));
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToJson(request, true); ResponseEntity<Resource> response = controller.convertPdfToJson(request, true);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
verify(pdfJsonConversionService).convertPdfToJson(pdfFile, true); verify(pdfJsonConversionService)
.convertPdfToJson(eq(pdfFile), eq(true), any(OutputStream.class));
} }
@Test @Test
@@ -125,9 +143,16 @@ class ConvertPdfJsonControllerTest {
GeneralFile request = new GeneralFile(); GeneralFile request = new GeneralFile();
request.setFileInput(jsonFile); request.setFileInput(jsonFile);
when(pdfJsonConversionService.convertJsonToPdf(jsonFile)).thenReturn(pdfBytes); doAnswer(
inv -> {
OutputStream os = inv.getArgument(1, OutputStream.class);
os.write(pdfBytes);
return null;
})
.when(pdfJsonConversionService)
.convertJsonToPdf(eq(jsonFile), any(OutputStream.class));
ResponseEntity<StreamingResponseBody> response = controller.convertJsonToPdf(request); ResponseEntity<Resource> response = controller.convertJsonToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
@@ -150,10 +175,16 @@ class ConvertPdfJsonControllerTest {
PDFFile request = new PDFFile(); PDFFile request = new PDFFile();
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
when(pdfJsonConversionService.extractDocumentMetadata(eq(pdfFile), any(String.class))) doAnswer(
.thenReturn(jsonBytes); inv -> {
OutputStream os = inv.getArgument(2, OutputStream.class);
os.write(jsonBytes);
return null;
})
.when(pdfJsonConversionService)
.extractDocumentMetadata(eq(pdfFile), any(String.class), any(OutputStream.class));
ResponseEntity<StreamingResponseBody> response = controller.extractPdfMetadata(request); ResponseEntity<Resource> response = controller.extractPdfMetadata(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
@@ -175,9 +206,16 @@ class ConvertPdfJsonControllerTest {
byte[] jsonBytes = "{\"content\":[]}".getBytes(); byte[] jsonBytes = "{\"content\":[]}".getBytes();
String jobId = "test-job-id"; String jobId = "test-job-id";
when(pdfJsonConversionService.extractSinglePage(jobId, 1)).thenReturn(jsonBytes); doAnswer(
inv -> {
OutputStream os = inv.getArgument(2, OutputStream.class);
os.write(jsonBytes);
return null;
})
.when(pdfJsonConversionService)
.extractSinglePage(eq(jobId), anyInt(), any(OutputStream.class));
ResponseEntity<StreamingResponseBody> response = controller.extractSinglePage(jobId, 1); ResponseEntity<Resource> response = controller.extractSinglePage(jobId, 1);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
@@ -188,9 +226,16 @@ class ConvertPdfJsonControllerTest {
byte[] jsonBytes = "{\"fonts\":[]}".getBytes(); byte[] jsonBytes = "{\"fonts\":[]}".getBytes();
String jobId = "test-job-id"; String jobId = "test-job-id";
when(pdfJsonConversionService.extractPageFonts(jobId, 1)).thenReturn(jsonBytes); doAnswer(
inv -> {
OutputStream os = inv.getArgument(2, OutputStream.class);
os.write(jsonBytes);
return null;
})
.when(pdfJsonConversionService)
.extractPageFonts(eq(jobId), anyInt(), any(OutputStream.class));
ResponseEntity<StreamingResponseBody> response = controller.extractPageFonts(jobId, 1); ResponseEntity<Resource> response = controller.extractPageFonts(jobId, 1);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
@@ -21,10 +21,11 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.converters.SvgToPdfRequest; import stirling.software.SPDF.model.api.converters.SvgToPdfRequest;
import stirling.software.SPDF.utils.SvgToPdf; import stirling.software.SPDF.utils.SvgToPdf;
@@ -37,14 +38,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConvertSvgToPDFTest { class ConvertSvgToPDFTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -75,7 +77,7 @@ class ConvertSvgToPDFTest {
SvgToPdfRequest request = new SvgToPdfRequest(); SvgToPdfRequest request = new SvgToPdfRequest();
request.setFileInput(null); request.setFileInput(null);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue( assertTrue(
@@ -88,7 +90,7 @@ class ConvertSvgToPDFTest {
SvgToPdfRequest request = new SvgToPdfRequest(); SvgToPdfRequest request = new SvgToPdfRequest();
request.setFileInput(new MockMultipartFile[0]); request.setFileInput(new MockMultipartFile[0]);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
} }
@@ -102,7 +104,7 @@ class ConvertSvgToPDFTest {
request.setFileInput(new MockMultipartFile[] {txtFile}); request.setFileInput(new MockMultipartFile[] {txtFile});
request.setCombineIntoSinglePdf(false); request.setCombineIntoSinglePdf(false);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue( assertTrue(
@@ -118,7 +120,7 @@ class ConvertSvgToPDFTest {
request.setFileInput(new MockMultipartFile[] {emptyFile}); request.setFileInput(new MockMultipartFile[] {emptyFile});
request.setCombineIntoSinglePdf(false); request.setCombineIntoSinglePdf(false);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
} }
@@ -141,7 +143,7 @@ class ConvertSvgToPDFTest {
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes)) when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes))
.thenReturn(processedPdf); .thenReturn(processedPdf);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf); ResponseEntity<Resource> expectedResponse = streamingOk(processedPdf);
try (MockedStatic<SvgToPdf> svgMock = Mockito.mockStatic(SvgToPdf.class); try (MockedStatic<SvgToPdf> svgMock = Mockito.mockStatic(SvgToPdf.class);
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class); MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
@@ -159,7 +161,7 @@ class ConvertSvgToPDFTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -188,7 +190,7 @@ class ConvertSvgToPDFTest {
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(combinedPdf)) when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(combinedPdf))
.thenReturn(processedPdf); .thenReturn(processedPdf);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf); ResponseEntity<Resource> expectedResponse = streamingOk(processedPdf);
try (MockedStatic<SvgToPdf> svgMock = Mockito.mockStatic(SvgToPdf.class); try (MockedStatic<SvgToPdf> svgMock = Mockito.mockStatic(SvgToPdf.class);
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class); MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
@@ -206,7 +208,7 @@ class ConvertSvgToPDFTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
} }
@@ -221,7 +223,7 @@ class ConvertSvgToPDFTest {
request.setFileInput(new MockMultipartFile[] {nullNameFile}); request.setFileInput(new MockMultipartFile[] {nullNameFile});
request.setCombineIntoSinglePdf(false); request.setCombineIntoSinglePdf(false);
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
} }
@@ -238,7 +240,7 @@ class ConvertSvgToPDFTest {
when(svgSanitizer.sanitize(svgContent)).thenThrow(new IOException("sanitization error")); when(svgSanitizer.sanitize(svgContent)).thenThrow(new IOException("sanitization error"));
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request); ResponseEntity<Resource> response = controller.convertSvgToPdf(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
} }
@@ -30,12 +30,13 @@ import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
@@ -49,14 +50,15 @@ import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager; import stirling.software.common.util.TempFileManager;
public class ConvertWebsiteToPdfTest { public class ConvertWebsiteToPdfTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -24,10 +24,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.model.api.converters.PdfVectorExportRequest; import stirling.software.SPDF.model.api.converters.PdfVectorExportRequest;
@@ -122,8 +122,7 @@ class PdfVectorExportControllerTest {
PdfVectorExportRequest request = new PdfVectorExportRequest(); PdfVectorExportRequest request = new PdfVectorExportRequest();
request.setFileInput(file); request.setFileInput(file);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.convertGhostscriptInputsToPdf(request);
controller.convertGhostscriptInputsToPdf(request);
assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK);
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF); assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF);
@@ -140,13 +139,14 @@ class PdfVectorExportControllerTest {
PdfVectorExportRequest request = new PdfVectorExportRequest(); PdfVectorExportRequest request = new PdfVectorExportRequest();
request.setFileInput(file); request.setFileInput(file);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.convertGhostscriptInputsToPdf(request);
controller.convertGhostscriptInputsToPdf(request);
assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK);
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF); assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF);
java.io.ByteArrayOutputStream baosVerify = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baosVerify = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baosVerify); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baosVerify);
}
assertThat(baosVerify.toByteArray()).contains(content); assertThat(baosVerify.toByteArray()).contains(content);
} }
@@ -13,11 +13,12 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.PDFComparisonAndCount; import stirling.software.SPDF.model.api.PDFComparisonAndCount;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
@@ -32,14 +33,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class FilterControllerTest { class FilterControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -72,7 +74,7 @@ class FilterControllerTest {
PDDocument mockDoc = mock(PDDocument.class); PDDocument mockDoc = mock(PDDocument.class);
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(new byte[] {1, 2, 3}); ResponseEntity<Resource> expectedResponse = streamingOk(new byte[] {1, 2, 3});
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class); try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class);
MockedStatic<WebResponseUtils> webMock = mockStatic(WebResponseUtils.class)) { MockedStatic<WebResponseUtils> webMock = mockStatic(WebResponseUtils.class)) {
@@ -84,7 +86,7 @@ class FilterControllerTest {
mockDoc, "test.pdf", tempFileManager)) mockDoc, "test.pdf", tempFileManager))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> result = filterController.containsText(request); ResponseEntity<Resource> result = filterController.containsText(request);
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertArrayEquals(new byte[] {1, 2, 3}, drainBody(result)); assertArrayEquals(new byte[] {1, 2, 3}, drainBody(result));
@@ -104,7 +106,7 @@ class FilterControllerTest {
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class)) { try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class)) {
pdfUtilsMock.when(() -> PdfUtils.hasText(mockDoc, "all", "missing")).thenReturn(false); pdfUtilsMock.when(() -> PdfUtils.hasText(mockDoc, "all", "missing")).thenReturn(false);
ResponseEntity<StreamingResponseBody> result = filterController.containsText(request); ResponseEntity<Resource> result = filterController.containsText(request);
assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode()); assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode());
assertNull(result.getBody()); assertNull(result.getBody());
@@ -122,7 +124,7 @@ class FilterControllerTest {
PDDocument mockDoc = mock(PDDocument.class); PDDocument mockDoc = mock(PDDocument.class);
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc);
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(new byte[] {4, 5, 6}); ResponseEntity<Resource> expectedResponse = streamingOk(new byte[] {4, 5, 6});
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class); try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class);
MockedStatic<WebResponseUtils> webMock = mockStatic(WebResponseUtils.class)) { MockedStatic<WebResponseUtils> webMock = mockStatic(WebResponseUtils.class)) {
@@ -134,7 +136,7 @@ class FilterControllerTest {
mockDoc, "test.pdf", tempFileManager)) mockDoc, "test.pdf", tempFileManager))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> result = filterController.containsImage(request); ResponseEntity<Resource> result = filterController.containsImage(request);
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertArrayEquals(new byte[] {4, 5, 6}, drainBody(result)); assertArrayEquals(new byte[] {4, 5, 6}, drainBody(result));
@@ -153,7 +155,7 @@ class FilterControllerTest {
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class)) { try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class)) {
pdfUtilsMock.when(() -> PdfUtils.hasImages(mockDoc, "1")).thenReturn(false); pdfUtilsMock.when(() -> PdfUtils.hasImages(mockDoc, "1")).thenReturn(false);
ResponseEntity<StreamingResponseBody> result = filterController.containsImage(request); ResponseEntity<Resource> result = filterController.containsImage(request);
assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode()); assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode());
} }
@@ -22,10 +22,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFile;
@@ -37,14 +38,15 @@ import tools.jackson.databind.json.JsonMapper;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@DisplayName("FormFillController Tests") @DisplayName("FormFillController Tests")
class FormFillControllerTest { class FormFillControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -232,8 +234,7 @@ class FormFillControllerTest {
when(pdfDocumentFactory.load(eq(file))).thenReturn(doc); when(pdfDocumentFactory.load(eq(file))).thenReturn(doc);
byte[] payload = "{\"field1\":\"value1\"}".getBytes(); byte[] payload = "{\"field1\":\"value1\"}".getBytes();
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.fillForm(file, payload, false);
controller.fillForm(file, payload, false);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull(); assertThat(response.getBody()).isNotNull();
@@ -246,7 +247,7 @@ class FormFillControllerTest {
PDDocument doc = createMinimalPdf(); PDDocument doc = createMinimalPdf();
when(pdfDocumentFactory.load(eq(file))).thenReturn(doc); when(pdfDocumentFactory.load(eq(file))).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.fillForm(file, null, false); ResponseEntity<Resource> response = controller.fillForm(file, null, false);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -287,7 +288,7 @@ class FormFillControllerTest {
when(pdfDocumentFactory.load(eq(file))).thenReturn(doc); when(pdfDocumentFactory.load(eq(file))).thenReturn(doc);
byte[] payload = "[\"field1\"]".getBytes(); byte[] payload = "[\"field1\"]".getBytes();
ResponseEntity<StreamingResponseBody> response = controller.deleteFields(file, payload); ResponseEntity<Resource> response = controller.deleteFields(file, payload);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -323,8 +324,7 @@ class FormFillControllerTest {
String json = String json =
"[{\"targetName\":\"f1\",\"name\":null,\"label\":null,\"type\":null," "[{\"targetName\":\"f1\",\"name\":null,\"label\":null,\"type\":null,"
+ "\"required\":null,\"multiSelect\":null,\"options\":null,\"defaultValue\":\"newVal\",\"tooltip\":null}]"; + "\"required\":null,\"multiSelect\":null,\"options\":null,\"defaultValue\":\"newVal\",\"tooltip\":null}]";
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.modifyFields(file, json.getBytes());
controller.modifyFields(file, json.getBytes());
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -17,12 +17,13 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.misc.AddAttachmentRequest; import stirling.software.SPDF.model.api.misc.AddAttachmentRequest;
import stirling.software.SPDF.service.AttachmentServiceInterface; import stirling.software.SPDF.service.AttachmentServiceInterface;
@@ -33,14 +34,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class AttachmentControllerTest { class AttachmentControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -100,8 +102,7 @@ class AttachmentControllerTest {
List<MultipartFile> attachments = List.of(attachment1, attachment2); List<MultipartFile> attachments = List.of(attachment1, attachment2);
request.setAttachments(attachments); request.setAttachments(attachments);
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("modified PDF content".getBytes());
streamingOk("modified PDF content".getBytes());
when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument); when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument);
when(pdfAttachmentService.addAttachment(mockDocument, attachments)) when(pdfAttachmentService.addAttachment(mockDocument, attachments))
@@ -118,8 +119,7 @@ class AttachmentControllerTest {
any(TempFileManager.class))) any(TempFileManager.class)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = attachmentController.addAttachments(request);
attachmentController.addAttachments(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -134,8 +134,7 @@ class AttachmentControllerTest {
List<MultipartFile> attachments = List.of(attachment1); List<MultipartFile> attachments = List.of(attachment1);
request.setAttachments(attachments); request.setAttachments(attachments);
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("modified PDF content".getBytes());
streamingOk("modified PDF content".getBytes());
when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument); when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument);
when(pdfAttachmentService.addAttachment(mockDocument, attachments)) when(pdfAttachmentService.addAttachment(mockDocument, attachments))
@@ -152,8 +151,7 @@ class AttachmentControllerTest {
any(TempFileManager.class))) any(TempFileManager.class)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = attachmentController.addAttachments(request);
attachmentController.addAttachments(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -23,11 +23,12 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest; import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -36,14 +37,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class AutoRenameControllerTest { class AutoRenameControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -101,7 +103,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -127,7 +129,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -140,7 +142,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -188,7 +190,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// The largest font text should be used as title (URL-encoded in Content-Disposition) // The largest font text should be used as title (URL-encoded in Content-Disposition)
@@ -206,7 +208,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// Should fallback to original filename since header is too long // Should fallback to original filename since header is too long
@@ -222,7 +224,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// Special characters should be sanitized // Special characters should be sanitized
@@ -248,7 +250,7 @@ class AutoRenameControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request); ResponseEntity<Resource> response = controller.extractHeader(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -23,11 +23,12 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -36,14 +37,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class DecompressPdfControllerTest { class DecompressPdfControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -97,7 +99,7 @@ class DecompressPdfControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request); ResponseEntity<Resource> response = controller.decompressPdf(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -116,7 +118,7 @@ class DecompressPdfControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request); ResponseEntity<Resource> response = controller.decompressPdf(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -147,7 +149,7 @@ class DecompressPdfControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request); ResponseEntity<Resource> response = controller.decompressPdf(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
String contentDisposition = response.getHeaders().getFirst("Content-Disposition"); String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
@@ -183,7 +185,7 @@ class DecompressPdfControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request); ResponseEntity<Resource> response = controller.decompressPdf(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
try (PDDocument result = Loader.loadPDF(drainBody(response))) { try (PDDocument result = Loader.loadPDF(drainBody(response))) {
@@ -200,7 +202,7 @@ class DecompressPdfControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request); ResponseEntity<Resource> response = controller.decompressPdf(request);
assertThat(response.getBody()).isNotNull(); assertThat(response.getBody()).isNotNull();
// Decompressed PDF should generally be larger or equal to compressed // Decompressed PDF should generally be larger or equal to compressed
@@ -216,7 +218,7 @@ class DecompressPdfControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request); ResponseEntity<Resource> response = controller.decompressPdf(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -25,11 +25,12 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.misc.FlattenRequest; import stirling.software.SPDF.model.api.misc.FlattenRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -38,14 +39,15 @@ import stirling.software.common.util.TempFileManager;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class FlattenControllerTest { class FlattenControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -98,7 +100,7 @@ class FlattenControllerTest {
PDDocument doc = Loader.loadPDF(file.getBytes()); PDDocument doc = Loader.loadPDF(file.getBytes());
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -118,7 +120,7 @@ class FlattenControllerTest {
when(doc.getDocumentCatalog()).thenReturn(catalog); when(doc.getDocumentCatalog()).thenReturn(catalog);
when(catalog.getAcroForm()).thenReturn(null); when(catalog.getAcroForm()).thenReturn(null);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
verify(doc).close(); verify(doc).close();
@@ -138,7 +140,7 @@ class FlattenControllerTest {
when(doc.getDocumentCatalog()).thenReturn(catalog); when(doc.getDocumentCatalog()).thenReturn(catalog);
when(catalog.getAcroForm()).thenReturn(form); when(catalog.getAcroForm()).thenReturn(form);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
verify(form).flatten(); verify(form).flatten();
@@ -170,7 +172,7 @@ class FlattenControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(drainBody(response)).isNotEmpty(); assertThat(drainBody(response)).isNotEmpty();
@@ -188,7 +190,7 @@ class FlattenControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -206,7 +208,7 @@ class FlattenControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -224,7 +226,7 @@ class FlattenControllerTest {
when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.load(file)).thenReturn(doc);
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
ResponseEntity<StreamingResponseBody> response = controller.flatten(request); ResponseEntity<Resource> response = controller.flatten(request);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@@ -23,11 +23,12 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest; import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -37,14 +38,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class OverlayImageControllerTest { class OverlayImageControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -108,8 +110,7 @@ class OverlayImageControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("result".getBytes());
streamingOk("result".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -117,7 +118,7 @@ class OverlayImageControllerTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request); ResponseEntity<Resource> response = controller.overlayImage(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -136,7 +137,7 @@ class OverlayImageControllerTest {
when(pdfDocumentFactory.load(any(byte[].class))).thenThrow(new IOException("bad PDF")); when(pdfDocumentFactory.load(any(byte[].class))).thenThrow(new IOException("bad PDF"));
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request); ResponseEntity<Resource> response = controller.overlayImage(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
} }
@@ -157,8 +158,7 @@ class OverlayImageControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("result".getBytes());
streamingOk("result".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -166,7 +166,7 @@ class OverlayImageControllerTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request); ResponseEntity<Resource> response = controller.overlayImage(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -189,8 +189,7 @@ class OverlayImageControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("result".getBytes());
streamingOk("result".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -198,7 +197,7 @@ class OverlayImageControllerTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request); ResponseEntity<Resource> response = controller.overlayImage(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -221,8 +220,7 @@ class OverlayImageControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("result".getBytes());
streamingOk("result".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -231,7 +229,7 @@ class OverlayImageControllerTest {
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
// Should not throw - coordinates are passed to contentStream.drawImage // Should not throw - coordinates are passed to contentStream.drawImage
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request); ResponseEntity<Resource> response = controller.overlayImage(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -17,12 +17,13 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest; import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService; import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
@@ -34,14 +35,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ReplaceAndInvertColorControllerTest { class ReplaceAndInvertColorControllerTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -96,7 +98,7 @@ class ReplaceAndInvertColorControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes); ResponseEntity<Resource> expectedResponse = streamingOk(resultBytes);
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -104,8 +106,7 @@ class ReplaceAndInvertColorControllerTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.replaceAndInvertColor(request);
controller.replaceAndInvertColor(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -132,7 +133,7 @@ class ReplaceAndInvertColorControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes); ResponseEntity<Resource> expectedResponse = streamingOk(resultBytes);
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -140,8 +141,7 @@ class ReplaceAndInvertColorControllerTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.replaceAndInvertColor(request);
controller.replaceAndInvertColor(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -166,7 +166,7 @@ class ReplaceAndInvertColorControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes); ResponseEntity<Resource> expectedResponse = streamingOk(resultBytes);
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -174,8 +174,7 @@ class ReplaceAndInvertColorControllerTest {
any(TempFile.class), anyString())) any(TempFile.class), anyString()))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = ResponseEntity<Resource> response = controller.replaceAndInvertColor(request);
controller.replaceAndInvertColor(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -205,7 +204,7 @@ class ReplaceAndInvertColorControllerTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes); ResponseEntity<Resource> expectedResponse = streamingOk(resultBytes);
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -19,11 +19,12 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -33,14 +34,15 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ShowJavascriptTest { class ShowJavascriptTest {
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) { private static ResponseEntity<Resource> streamingOk(byte[] bytes) {
return ResponseEntity.ok(out -> out.write(bytes)); return ResponseEntity.ok(new ByteArrayResource(bytes));
} }
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response) private static byte[] drainBody(ResponseEntity<Resource> response) throws java.io.IOException {
throws java.io.IOException {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
response.getBody().writeTo(baos); try (java.io.InputStream __in = response.getBody().getInputStream()) {
__in.transferTo(baos);
}
return baos.toByteArray(); return baos.toByteArray();
} }
@@ -86,8 +88,7 @@ class ShowJavascriptTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("no js".getBytes());
streamingOk("no js".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -97,7 +98,7 @@ class ShowJavascriptTest {
eq(MediaType.TEXT_PLAIN))) eq(MediaType.TEXT_PLAIN)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request); ResponseEntity<Resource> response = showJavascript.extractHeader(request);
assertNotNull(response); assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -129,8 +130,7 @@ class ShowJavascriptTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("js content".getBytes());
streamingOk("js content".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -140,7 +140,7 @@ class ShowJavascriptTest {
eq(MediaType.TEXT_PLAIN))) eq(MediaType.TEXT_PLAIN)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request); ResponseEntity<Resource> response = showJavascript.extractHeader(request);
assertNotNull(response); assertNotNull(response);
mockedWebResponse.verify( mockedWebResponse.verify(
@@ -160,8 +160,7 @@ class ShowJavascriptTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("no js".getBytes());
streamingOk("no js".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -171,7 +170,7 @@ class ShowJavascriptTest {
eq(MediaType.TEXT_PLAIN))) eq(MediaType.TEXT_PLAIN)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request); ResponseEntity<Resource> response = showJavascript.extractHeader(request);
assertNotNull(response); assertNotNull(response);
mockedWebResponse.verify( mockedWebResponse.verify(
@@ -200,8 +199,7 @@ class ShowJavascriptTest {
try (MockedStatic<WebResponseUtils> mockedWebResponse = try (MockedStatic<WebResponseUtils> mockedWebResponse =
mockStatic(WebResponseUtils.class)) { mockStatic(WebResponseUtils.class)) {
ResponseEntity<StreamingResponseBody> expectedResponse = ResponseEntity<Resource> expectedResponse = streamingOk("no js".getBytes());
streamingOk("no js".getBytes());
mockedWebResponse mockedWebResponse
.when( .when(
() -> () ->
@@ -211,7 +209,7 @@ class ShowJavascriptTest {
eq(MediaType.TEXT_PLAIN))) eq(MediaType.TEXT_PLAIN)))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request); ResponseEntity<Resource> response = showJavascript.extractHeader(request);
assertNotNull(response); assertNotNull(response);
mockedWebResponse.verify( mockedWebResponse.verify(
@@ -16,9 +16,9 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -67,7 +67,7 @@ class UnlockPDFFormsControllerTest {
PDFFile file = new PDFFile(); PDFFile file = new PDFFile();
file.setFileInput(mockPdfFile); file.setFileInput(mockPdfFile);
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file); ResponseEntity<Resource> response = controller.unlockPDFForms(file);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -85,7 +85,7 @@ class UnlockPDFFormsControllerTest {
PDFFile file = new PDFFile(); PDFFile file = new PDFFile();
file.setFileInput(mockPdfFile); file.setFileInput(mockPdfFile);
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file); ResponseEntity<Resource> response = controller.unlockPDFForms(file);
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatusCode().value()); assertEquals(200, response.getStatusCode().value());
@@ -99,7 +99,7 @@ class UnlockPDFFormsControllerTest {
PDFFile file = new PDFFile(); PDFFile file = new PDFFile();
file.setFileInput(mockPdfFile); file.setFileInput(mockPdfFile);
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file); ResponseEntity<Resource> response = controller.unlockPDFForms(file);
// Controller catches exceptions and returns null // Controller catches exceptions and returns null
assertNull(response); assertNull(response);
@@ -114,7 +114,7 @@ class UnlockPDFFormsControllerTest {
PDFFile file = new PDFFile(); PDFFile file = new PDFFile();
file.setFileInput(mockPdfFile); file.setFileInput(mockPdfFile);
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file); ResponseEntity<Resource> response = controller.unlockPDFForms(file);
assertNotNull(response); assertNotNull(response);
String contentDisposition = response.getHeaders().getFirst("Content-Disposition"); String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
@@ -133,7 +133,7 @@ class UnlockPDFFormsControllerTest {
PDFFile file = new PDFFile(); PDFFile file = new PDFFile();
file.setFileInput(mockPdfFile); file.setFileInput(mockPdfFile);
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file); ResponseEntity<Resource> response = controller.unlockPDFForms(file);
assertNotNull(response); assertNotNull(response);
assertTrue(acroForm.getNeedAppearances()); assertTrue(acroForm.getNeedAppearances());

Some files were not shown because too many files have changed in this diff Show More