diff --git a/app/common/src/main/java/stirling/software/common/util/PDFService.java b/app/common/src/main/java/stirling/software/common/util/PDFService.java new file mode 100644 index 000000000..255b4e214 --- /dev/null +++ b/app/common/src/main/java/stirling/software/common/util/PDFService.java @@ -0,0 +1,35 @@ +package stirling.software.common.util; + +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.multipdf.PDFMergerUtility; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +import stirling.software.common.service.CustomPDFDocumentFactory; + +@Service +@RequiredArgsConstructor +public class PDFService { + + private final CustomPDFDocumentFactory pdfDocumentFactory; + + /* + * Merge multiple PDF documents into a single PDF document + * + * @param documents List of PDDocument to be merged + * @return Merged PDDocument + * @throws IOException If an error occurs during merging + */ + public PDDocument mergeDocuments(List documents) throws IOException { + PDDocument merged = pdfDocumentFactory.createNewDocument(); + PDFMergerUtility merger = new PDFMergerUtility(); + for (PDDocument doc : documents) { + merger.appendDocument(merged, doc); + } + return merged; + } +} diff --git a/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java b/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java index 745f5d5ec..466b28e69 100644 --- a/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java @@ -4,6 +4,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import org.apache.pdfbox.pdmodel.PDDocument; import org.springframework.http.HttpHeaders; @@ -11,9 +13,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import io.github.pixee.security.Filenames; +import lombok.extern.slf4j.Slf4j; + +@Slf4j public class WebResponseUtils { public static ResponseEntity baosToWebResponse( @@ -64,4 +70,59 @@ public class WebResponseUtils { return baosToWebResponse(baos, docName); } + + /** + * Convert a File to a web response (PDF default). + * + * @param outputTempFile The temporary file to be sent as a response. + * @param docName The name of the document. + * @return A ResponseEntity containing the file as a resource. + */ + public static ResponseEntity pdfFileToWebResponse( + TempFile outputTempFile, String docName) throws IOException { + return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_PDF); + } + + /** + * Convert a File to a web response (ZIP default). + * + * @param outputTempFile The temporary file to be sent as a response. + * @param docName The name of the document. + * @return A ResponseEntity containing the file as a resource. + */ + public static ResponseEntity zipFileToWebResponse( + TempFile outputTempFile, String docName) throws IOException { + return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_OCTET_STREAM); + } + + /** + * Convert a File to a web response with explicit media type (e.g., ZIP). + * + * @param outputTempFile The temporary file to be sent as a response. + * @param docName The name of the document. + * @param mediaType The content type to set on the response. + * @return A ResponseEntity containing the file as a resource. + */ + public static ResponseEntity fileToWebResponse( + TempFile outputTempFile, String docName, MediaType mediaType) throws IOException { + + Path path = outputTempFile.getFile().toPath().normalize(); + long len = Files.size(path); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(mediaType); + headers.setContentLength(len); + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + docName + "\""); + + StreamingResponseBody body = + os -> { + try (os) { + Files.copy(path, os); + os.flush(); + } finally { + outputTempFile.close(); + } + }; + + return new ResponseEntity<>(body, headers, HttpStatus.OK); + } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 1377b3812..a15c41221 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -1,6 +1,5 @@ package stirling.software.SPDF.controller.api; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -27,6 +26,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; 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.tags.Tag; @@ -37,8 +37,9 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.general.MergePdfsRequest; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.ExceptionUtils; -import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.PdfErrorUtils; +import stirling.software.common.util.TempFile; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -49,6 +50,7 @@ import stirling.software.common.util.WebResponseUtils; public class MergeController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; // Merges a list of PDDocument objects into a single PDDocument public PDDocument mergeDocuments(List documents) throws IOException { @@ -63,56 +65,54 @@ public class MergeController { // Returns a comparator for sorting MultipartFile arrays based on the given sort type private Comparator getSortComparator(String sortType) { - switch (sortType) { - case "byFileName": - return Comparator.comparing(MultipartFile::getOriginalFilename); - case "byDateModified": - return (file1, file2) -> { - try { - BasicFileAttributes attr1 = - Files.readAttributes( - Paths.get(file1.getOriginalFilename()), - BasicFileAttributes.class); - BasicFileAttributes attr2 = - Files.readAttributes( - Paths.get(file2.getOriginalFilename()), - BasicFileAttributes.class); - return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime()); - } catch (IOException e) { - return 0; // If there's an error, treat them as equal - } - }; - case "byDateCreated": - return (file1, file2) -> { - try { - BasicFileAttributes attr1 = - Files.readAttributes( - Paths.get(file1.getOriginalFilename()), - BasicFileAttributes.class); - BasicFileAttributes attr2 = - Files.readAttributes( - Paths.get(file2.getOriginalFilename()), - BasicFileAttributes.class); - return attr1.creationTime().compareTo(attr2.creationTime()); - } catch (IOException e) { - return 0; // If there's an error, treat them as equal - } - }; - case "byPDFTitle": - return (file1, file2) -> { - try (PDDocument doc1 = pdfDocumentFactory.load(file1); - PDDocument doc2 = pdfDocumentFactory.load(file2)) { - String title1 = doc1.getDocumentInformation().getTitle(); - String title2 = doc2.getDocumentInformation().getTitle(); - return title1.compareTo(title2); - } catch (IOException e) { - return 0; - } - }; - case "orderProvided": - default: - return (file1, file2) -> 0; // Default is the order provided - } + return switch (sortType) { + case "byFileName" -> Comparator.comparing(MultipartFile::getOriginalFilename); + case "byDateModified" -> + (file1, file2) -> { + try { + BasicFileAttributes attr1 = + Files.readAttributes( + Paths.get(file1.getOriginalFilename()), + BasicFileAttributes.class); + BasicFileAttributes attr2 = + Files.readAttributes( + Paths.get(file2.getOriginalFilename()), + BasicFileAttributes.class); + return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime()); + } catch (IOException e) { + return 0; // If there's an error, treat them as equal + } + }; + case "byDateCreated" -> + (file1, file2) -> { + try { + BasicFileAttributes attr1 = + Files.readAttributes( + Paths.get(file1.getOriginalFilename()), + BasicFileAttributes.class); + BasicFileAttributes attr2 = + Files.readAttributes( + Paths.get(file2.getOriginalFilename()), + BasicFileAttributes.class); + return attr1.creationTime().compareTo(attr2.creationTime()); + } catch (IOException e) { + return 0; // If there's an error, treat them as equal + } + }; + case "byPDFTitle" -> + (file1, file2) -> { + try (PDDocument doc1 = pdfDocumentFactory.load(file1); + PDDocument doc2 = pdfDocumentFactory.load(file2)) { + String title1 = doc1.getDocumentInformation().getTitle(); + String title2 = doc2.getDocumentInformation().getTitle(); + return title1.compareTo(title2); + } catch (IOException e) { + return 0; + } + }; + case "orderProvided" -> (file1, file2) -> 0; // Default is the order provided + default -> (file1, file2) -> 0; // Default is the order provided + }; } // Adds a table of contents to the merged document using filenames as chapter titles @@ -162,10 +162,11 @@ public class MergeController { "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" + " provided. Input:PDF Output:PDF Type:MISO") - public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest request) + public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest request) throws IOException { List filesToDelete = new ArrayList<>(); // List of temporary files to delete - File mergedTempFile = null; + TempFile mergedTempFile = null; + TempFile outputTempFile = null; PDDocument mergedDocument = null; boolean removeCertSign = Boolean.TRUE.equals(request.getRemoveCertSign()); @@ -183,14 +184,14 @@ public class MergeController { for (MultipartFile multipartFile : files) { totalSize += multipartFile.getSize(); File tempFile = - GeneralUtils.convertMultipartFileToFile( + tempFileManager.convertMultipartFileToFile( multipartFile); // Convert MultipartFile to File filesToDelete.add(tempFile); // Add temp file to the list for later deletion mergerUtility.addSource(tempFile); // Add source file to the merger utility } - mergedTempFile = Files.createTempFile("merged-", ".pdf").toFile(); - mergerUtility.setDestinationFileName(mergedTempFile.getAbsolutePath()); + mergedTempFile = new TempFile(tempFileManager, ".pdf"); + mergerUtility.setDestinationFileName(mergedTempFile.getFile().getAbsolutePath()); try { mergerUtility.mergeDocuments( @@ -205,7 +206,7 @@ public class MergeController { } // Load the merged PDF document - mergedDocument = pdfDocumentFactory.load(mergedTempFile); + mergedDocument = pdfDocumentFactory.load(mergedTempFile.getFile()); // Remove signatures if removeCertSign is true if (removeCertSign) { @@ -214,7 +215,7 @@ public class MergeController { if (acroForm != null) { List fieldsToRemove = acroForm.getFields().stream() - .filter(field -> field instanceof PDSignatureField) + .filter(PDSignatureField.class::isInstance) .toList(); if (!fieldsToRemove.isEmpty()) { @@ -230,16 +231,15 @@ public class MergeController { addTableOfContents(mergedDocument, files); } - // Save the modified document to a new ByteArrayOutputStream - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - mergedDocument.save(baos); + // Save the modified document to a temporary file + outputTempFile = new TempFile(tempFileManager, ".pdf"); + mergedDocument.save(outputTempFile.getFile()); String mergedFileName = files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged_unsigned.pdf"; - return WebResponseUtils.baosToWebResponse( - baos, mergedFileName); // Return the modified PDF - + return WebResponseUtils.pdfFileToWebResponse( + outputTempFile, mergedFileName); // Return the modified PDF as stream } catch (Exception ex) { if (ex instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) ex)) { log.warn("Corrupted PDF detected in merge pdf process: {}", ex.getMessage()); @@ -252,12 +252,10 @@ public class MergeController { mergedDocument.close(); // Close the merged document } for (File file : filesToDelete) { - if (file != null) { - Files.deleteIfExists(file.toPath()); // Delete temporary files - } + tempFileManager.deleteTempFile(file); // Delete temporary files } if (mergedTempFile != null) { - Files.deleteIfExists(mergedTempFile.toPath()); + mergedTempFile.close(); } } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 16a92a525..3446c23ba 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -30,6 +30,8 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.ExceptionUtils; +import stirling.software.common.util.TempFile; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -40,6 +42,7 @@ import stirling.software.common.util.WebResponseUtils; public class SplitPDFController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/split-pages") @Operation( @@ -55,8 +58,11 @@ public class SplitPDFController { PDDocument document = null; Path zipFile = null; List splitDocumentsBoas = new ArrayList<>(); + String filename; + TempFile outputTempFile = null; try { + outputTempFile = new TempFile(tempFileManager, ".zip"); MultipartFile file = request.getFileInput(); String pages = request.getPageNumbers(); @@ -105,12 +111,11 @@ public class SplitPDFController { // closing the original document document.close(); - zipFile = Files.createTempFile("split_documents", ".zip"); - - String filename = + filename = Filenames.toSimpleFileName(file.getOriginalFilename()) .replaceFirst("[.][^.]+$", ""); - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { + try (ZipOutputStream zipOut = + new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) { // loop through the split documents and write them to the zip file for (int i = 0; i < splitDocumentsBoas.size(); i++) { String fileName = filename + "_" + (i + 1) + ".pdf"; @@ -125,19 +130,13 @@ public class SplitPDFController { log.debug("Wrote split document {} to zip file", fileName); } - } catch (Exception e) { - log.error("Failed writing to zip", e); - throw e; } - - log.debug("Successfully created zip file with split documents: {}", zipFile.toString()); - byte[] data = Files.readAllBytes(zipFile); - Files.deleteIfExists(zipFile); - - // return the Resource in the response + log.debug( + "Successfully created zip file with split documents: {}", + outputTempFile.getPath()); + byte[] data = Files.readAllBytes(outputTempFile.getPath()); return WebResponseUtils.bytesToWebResponse( data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); - } finally { try { // Close the main document @@ -152,9 +151,9 @@ public class SplitPDFController { } } - // Delete temporary zip file - if (zipFile != null) { - Files.deleteIfExists(zipFile); + // Close the output temporary file + if (outputTempFile != null) { + outputTempFile.close(); } } catch (Exception e) { log.error("Error while cleaning up resources", e); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index ff15bc718..6758abecc 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -2,8 +2,8 @@ package stirling.software.SPDF.controller.api; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; @@ -17,13 +17,13 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import io.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; @@ -33,6 +33,9 @@ import lombok.RequiredArgsConstructor; import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.util.PDFService; +import stirling.software.common.util.TempFile; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -42,6 +45,8 @@ import stirling.software.common.util.WebResponseUtils; public class SplitPdfBySectionsController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; + private final PDFService pdfService; @PostMapping(value = "/split-pdf-by-sections", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation( @@ -50,8 +55,8 @@ public class SplitPdfBySectionsController { "Split each page of a PDF into smaller sections based on the user's choice" + " (halves, thirds, quarters, etc.), both vertically and horizontally." + " Input:PDF Output:ZIP-PDF Type:SISO") - public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) - throws Exception { + public ResponseEntity splitPdf( + @ModelAttribute SplitPdfBySectionsRequest request) throws Exception { List splitDocumentsBoas = new ArrayList<>(); MultipartFile file = request.getFileInput(); @@ -67,10 +72,14 @@ public class SplitPdfBySectionsController { Filenames.toSimpleFileName(file.getOriginalFilename()) .replaceFirst("[.][^.]+$", ""); if (merge) { - MergeController mergeController = new MergeController(pdfDocumentFactory); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - mergeController.mergeDocuments(splitDocuments).save(baos); - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf"); + TempFile tempFile = new TempFile(tempFileManager, ".pdf"); + try (PDDocument merged = pdfService.mergeDocuments(splitDocuments); + OutputStream out = Files.newOutputStream(tempFile.getPath())) { + merged.save(out); + for (PDDocument d : splitDocuments) d.close(); + sourceDocument.close(); + } + return WebResponseUtils.pdfFileToWebResponse(tempFile, filename + "_split.pdf"); } for (PDDocument doc : splitDocuments) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -81,10 +90,9 @@ public class SplitPdfBySectionsController { sourceDocument.close(); - Path zipFile = Files.createTempFile("split_documents", ".zip"); - byte[] data; - - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { + TempFile zipTempFile = new TempFile(tempFileManager, ".zip"); + try (ZipOutputStream zipOut = + new ZipOutputStream(Files.newOutputStream(zipTempFile.getPath()))) { int pageNum = 1; for (int i = 0; i < splitDocumentsBoas.size(); i++) { ByteArrayOutputStream baos = splitDocumentsBoas.get(i); @@ -98,15 +106,8 @@ public class SplitPdfBySectionsController { if (sectionNum == horiz * verti) pageNum++; } - - zipOut.finish(); - data = Files.readAllBytes(zipFile); - return WebResponseUtils.bytesToWebResponse( - data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM); - - } finally { - Files.deleteIfExists(zipFile); } + return WebResponseUtils.zipFileToWebResponse(zipTempFile, filename + "_split.zip"); } public List splitPdfPages( diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java index 25bee98a2..b2017f876 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java @@ -28,6 +28,8 @@ import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.GeneralUtils; +import stirling.software.common.util.TempFile; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -38,6 +40,7 @@ import stirling.software.common.util.WebResponseUtils; public class SplitPdfBySizeController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; @PostMapping(value = "/split-by-size-or-count", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation( @@ -54,89 +57,68 @@ public class SplitPdfBySizeController { log.debug("Starting PDF split process with request: {}", request); MultipartFile file = request.getFileInput(); - Path zipFile = Files.createTempFile("split_documents", ".zip"); - log.debug("Created temporary zip file: {}", zipFile); - String filename = Filenames.toSimpleFileName(file.getOriginalFilename()) .replaceFirst("[.][^.]+$", ""); log.debug("Base filename for output: {}", filename); - byte[] data = null; - try { - log.debug("Reading input file bytes"); - byte[] pdfBytes = file.getBytes(); - log.debug("Successfully read {} bytes from input file", pdfBytes.length); + try (TempFile zipTempFile = new TempFile(tempFileManager, ".zip")) { + Path zipFile = zipTempFile.getPath(); + log.debug("Created temporary zip file: {}", zipFile); + try { + log.debug("Reading input file bytes"); + byte[] pdfBytes = file.getBytes(); + log.debug("Successfully read {} bytes from input file", pdfBytes.length); - log.debug("Creating ZIP output stream"); - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { - log.debug("Loading PDF document"); - try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) { - log.debug( - "Successfully loaded PDF with {} pages", - sourceDocument.getNumberOfPages()); + log.debug("Creating ZIP output stream"); + try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { + log.debug("Loading PDF document"); + try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) { + log.debug( + "Successfully loaded PDF with {} pages", + sourceDocument.getNumberOfPages()); - int type = request.getSplitType(); - String value = request.getSplitValue(); - log.debug("Split type: {}, Split value: {}", type, value); + int type = request.getSplitType(); + String value = request.getSplitValue(); + log.debug("Split type: {}, Split value: {}", type, value); - if (type == 0) { - log.debug("Processing split by size"); - long maxBytes = GeneralUtils.convertSizeToBytes(value); - log.debug("Max bytes per document: {}", maxBytes); - handleSplitBySize(sourceDocument, maxBytes, zipOut, filename); - } else if (type == 1) { - log.debug("Processing split by page count"); - int pageCount = Integer.parseInt(value); - log.debug("Pages per document: {}", pageCount); - handleSplitByPageCount(sourceDocument, pageCount, zipOut, filename); - } else if (type == 2) { - log.debug("Processing split by document count"); - int documentCount = Integer.parseInt(value); - log.debug("Total number of documents: {}", documentCount); - handleSplitByDocCount(sourceDocument, documentCount, zipOut, filename); - } else { - log.error("Invalid split type: {}", type); - throw ExceptionUtils.createIllegalArgumentException( - "error.invalidArgument", - "Invalid argument: {0}", - "split type: " + type); + if (type == 0) { + log.debug("Processing split by size"); + long maxBytes = GeneralUtils.convertSizeToBytes(value); + log.debug("Max bytes per document: {}", maxBytes); + handleSplitBySize(sourceDocument, maxBytes, zipOut, filename); + } else if (type == 1) { + log.debug("Processing split by page count"); + int pageCount = Integer.parseInt(value); + log.debug("Pages per document: {}", pageCount); + handleSplitByPageCount(sourceDocument, pageCount, zipOut, filename); + } else if (type == 2) { + log.debug("Processing split by document count"); + int documentCount = Integer.parseInt(value); + log.debug("Total number of documents: {}", documentCount); + handleSplitByDocCount(sourceDocument, documentCount, zipOut, filename); + } else { + log.error("Invalid split type: {}", type); + throw ExceptionUtils.createIllegalArgumentException( + "error.invalidArgument", + "Invalid argument: {0}", + "split type: " + type); + } + log.debug("PDF splitting completed successfully"); } - - log.debug("PDF splitting completed successfully"); - } catch (Exception e) { - ExceptionUtils.logException("PDF document loading or processing", e); - throw e; } - } catch (IOException e) { - log.error("Error creating or writing to ZIP file", e); - throw e; - } - } catch (Exception e) { - ExceptionUtils.logException("PDF splitting process", e); - throw e; // Re-throw to ensure proper error response - } finally { - try { - log.debug("Reading ZIP file data"); - data = Files.readAllBytes(zipFile); + byte[] data = Files.readAllBytes(zipFile); log.debug("Successfully read {} bytes from ZIP file", data.length); - } catch (IOException e) { - log.error("Error reading ZIP file data", e); - } - try { - log.debug("Deleting temporary ZIP file"); - boolean deleted = Files.deleteIfExists(zipFile); - log.debug("Temporary ZIP file deleted: {}", deleted); - } catch (IOException e) { - log.error("Error deleting temporary ZIP file", e); + log.debug("Returning response with {} bytes of data", data.length); + return WebResponseUtils.bytesToWebResponse( + data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + } catch (Exception e) { + ExceptionUtils.logException("PDF splitting process", e); + throw e; // Re-throw to ensure proper error response } } - - log.debug("Returning response with {} bytes of data", data != null ? data.length : 0); - return WebResponseUtils.bytesToWebResponse( - data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); } private void handleSplitBySize( diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index b1e6a8dc5..bcc20e219 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -6,7 +6,6 @@ import java.awt.image.DataBufferInt; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -36,6 +35,8 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.util.TempFile; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -53,6 +54,7 @@ public class AutoSplitPdfController { "https://stirlingpdf.com")); private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; private static String decodeQRCode(BufferedImage bufferedImage) { LuminanceSource source; @@ -117,10 +119,10 @@ public class AutoSplitPdfController { PDDocument document = null; List splitDocuments = new ArrayList<>(); - Path zipFile = null; - byte[] data = null; + TempFile outputTempFile = null; try { + outputTempFile = new TempFile(tempFileManager, ".zip"); document = pdfDocumentFactory.load(file.getInputStream()); PDFRenderer pdfRenderer = new PDFRenderer(document); pdfRenderer.setSubsamplingAllowed(true); @@ -152,12 +154,12 @@ public class AutoSplitPdfController { // Remove split documents that have no pages splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0); - zipFile = Files.createTempFile("split_documents", ".zip"); String filename = Filenames.toSimpleFileName(file.getOriginalFilename()) .replaceFirst("[.][^.]+$", ""); - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { + try (ZipOutputStream zipOut = + new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) { for (int i = 0; i < splitDocuments.size(); i++) { String fileName = filename + "_" + (i + 1) + ".pdf"; PDDocument splitDocument = splitDocuments.get(i); @@ -173,10 +175,10 @@ public class AutoSplitPdfController { } } - data = Files.readAllBytes(zipFile); - + byte[] data = Files.readAllBytes(outputTempFile.getPath()); return WebResponseUtils.bytesToWebResponse( data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + } catch (Exception e) { log.error("Error in auto split", e); throw e; @@ -198,12 +200,8 @@ public class AutoSplitPdfController { } } - if (zipFile != null) { - try { - Files.deleteIfExists(zipFile); - } catch (IOException e) { - log.error("Error deleting temporary zip file", e); - } + if (outputTempFile != null) { + outputTempFile.close(); } } }