diff --git a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java index 96c4d2707..d6b0335dc 100644 --- a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java +++ b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java @@ -3,8 +3,6 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import io.github.pixee.security.SystemCommand; @@ -15,84 +13,90 @@ public class LibreOfficeListener { private static final LibreOfficeListener INSTANCE = new LibreOfficeListener(); private static final int LISTENER_PORT = 2002; + private long lastActivityTime; + private ListenerProcess listenerProcess; + public static LibreOfficeListener getInstance() { return INSTANCE; } - private ExecutorService executorService; - private long lastActivityTime; + public void start() throws IOException { + if (listenerProcess != null && listenerProcess.isRunning()) { + return; + } - private Process process; + listenerProcess = new StartListenerProcess(); + listenerProcess.start(); + lastActivityTime = System.currentTimeMillis(); - private LibreOfficeListener() {} + ActivityMonitor monitor = new ActivityMonitor(); + monitor.start(); + } - private boolean isListenerRunning() { - try { - System.out.println("waiting for listener to start"); - Socket socket = new Socket(); - socket.connect( - new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second - socket.close(); - return true; - } catch (IOException e) { - return false; + public synchronized void stop() { + if (listenerProcess != null && listenerProcess.isRunning()) { + listenerProcess.stop(); } } - public void start() throws IOException { - // Check if the listener is already running - if (process != null && process.isAlive()) { - return; + private class ActivityMonitor extends Thread { + @Override + public void run() { + while (true) { + long idleTime = System.currentTimeMillis() - lastActivityTime; + if (idleTime >= ACTIVITY_TIMEOUT) { + listenerProcess.stop(); + break; + } + try { + Thread.sleep(5000); // Check for inactivity every 5 seconds + } catch (InterruptedException e) { + break; + } + } } + } - // Start the listener process - process = SystemCommand.runCommand(Runtime.getRuntime(), "unoconv --listener"); - lastActivityTime = System.currentTimeMillis(); + interface ListenerProcess { + void start() throws IOException; + + void stop(); + + boolean isRunning(); + } + + private class StartListenerProcess implements ListenerProcess { + private Process process; - // Start a background thread to monitor the activity timeout - executorService = Executors.newSingleThreadExecutor(); - executorService.submit( - () -> { - while (true) { - long idleTime = System.currentTimeMillis() - lastActivityTime; - if (idleTime >= ACTIVITY_TIMEOUT) { - // If there has been no activity for too long, tear down the listener - process.destroy(); - break; - } - try { - Thread.sleep(5000); // Check for inactivity every 5 seconds - } catch (InterruptedException e) { - break; - } - } - }); - - // Wait for the listener to start up - long startTime = System.currentTimeMillis(); - long timeout = 30000; // Timeout after 30 seconds - while (System.currentTimeMillis() - startTime < timeout) { - if (isListenerRunning()) { - - lastActivityTime = System.currentTimeMillis(); - return; + @Override + public void start() throws IOException { + process = SystemCommand.runCommand(Runtime.getRuntime(), "unoconv --listener"); + } + + @Override + public void stop() { + if (process != null && process.isAlive()) { + process.destroy(); } - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } // Check every 1 second } - } - public synchronized void stop() { - // Stop the activity timeout monitor thread - executorService.shutdownNow(); + @Override + public boolean isRunning() { + return isListenerRunning(); + } + } - // Stop the listener process - if (process != null && process.isAlive()) { - process.destroy(); + private boolean isListenerRunning() { + try { + System.out.println("waiting for listener to start"); + Socket socket = new Socket(); + socket.connect( + new InetSocketAddress("localhost", LISTENER_PORT), + 1000); // Timeout after 1 second + socket.close(); + return true; + } catch (IOException e) { + return false; } } } diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 9a070e523..38509229c 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -32,30 +32,241 @@ public EndpointConfiguration( @Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) { this.applicationProperties = applicationProperties; this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled; - init(); + initialize(); processEnvironmentConfigs(); } + private void initialize() { + // Define endpoints for each group explicitly as String arrays + String[] pageOpsEndpoints = { + "remove-pages", + "merge-pdfs", + "split-pdfs", + "pdf-organizer", + "rotate-pdf", + "multi-page-layout", + "scale-pages", + "adjust-contrast", + "crop", + "auto-split-pdf", + "extract-page", + "pdf-to-single-page", + "split-by-size-or-count", + "overlay-pdf", + "split-pdf-by-sections" + }; + + String[] convertEndpoints = { + "pdf-to-img", + "img-to-pdf", + "pdf-to-pdfa", + "file-to-pdf", + "xlsx-to-pdf", + "pdf-to-word", + "pdf-to-presentation", + "pdf-to-text", + "pdf-to-html", + "pdf-to-xml", + "html-to-pdf", + "url-to-pdf", + "markdown-to-pdf", + "pdf-to-csv" + }; + + String[] securityEndpoints = { + "add-password", + "remove-password", + "change-permissions", + "add-watermark", + "cert-sign", + "sanitize-pdf", + "auto-redact" + }; + + String[] otherEndpoints = { + "ocr-pdf", + "add-image", + "compress-pdf", + "extract-images", + "change-metadata", + "extract-image-scans", + "sign", + "flatten", + "repair", + "remove-blanks", + "remove-annotations", + "compare", + "add-page-numbers", + "auto-rename", + "get-info-on-pdf", + "show-javascript" + }; + + String[] cliEndpoints = { + "compress-pdf", + "extract-image-scans", + "repair", + "pdf-to-pdfa", + "file-to-pdf", + "xlsx-to-pdf", + "pdf-to-word", + "pdf-to-presentation", + "pdf-to-text", + "pdf-to-html", + "pdf-to-xml", + "ocr-pdf", + "html-to-pdf", + "url-to-pdf", + "book-to-pdf", + "pdf-to-book" + }; + + String[] calibreEndpoints = { + "book-to-pdf", + "pdf-to-book" + }; + + String[] pythonEndpoints = { + "extract-image-scans", + "remove-blanks", + "html-to-pdf", + "url-to-pdf" + }; + + String[] openCVEndpoints = { + "extract-image-scans", + "remove-blanks" + }; + + String[] libreOfficeEndpoints = { + "repair", + "file-to-pdf", + "xlsx-to-pdf", + "pdf-to-word", + "pdf-to-presentation", + "pdf-to-text", + "pdf-to-html", + "pdf-to-xml" + }; + + String[] ocrmyPDFEndpoints = { + "compress-pdf", + "pdf-to-pdfa", + "ocr-pdf" + }; + + String[] javaEndpoints = { + "merge-pdfs", + "remove-pages", + "split-pdfs", + "pdf-organizer", + "rotate-pdf", + "pdf-to-img", + "img-to-pdf", + "add-password", + "remove-password", + "change-permissions", + "add-watermark", + "add-image", + "extract-images", + "change-metadata", + "cert-sign", + "multi-page-layout", + "scale-pages", + "add-page-numbers", + "auto-rename", + "auto-split-pdf", + "sanitize-pdf", + "crop", + "get-info-on-pdf", + "extract-page", + "pdf-to-single-page", + "markdown-to-pdf", + "show-javascript", + "auto-redact", + "pdf-to-csv", + "split-by-size-or-count", + "overlay-pdf", + "split-pdf-by-sections", + "remove-blanks" + }; + + String[] javascriptEndpoints = { + "pdf-organizer", + "sign", + "compare", + "adjust-contrast" + }; + + // Add each group with its respective endpoints + addEndpointsToGroup("PageOps", pageOpsEndpoints); + addEndpointsToGroup("Convert", convertEndpoints); + addEndpointsToGroup("Security", securityEndpoints); + addEndpointsToGroup("Other", otherEndpoints); + addEndpointsToGroup("CLI", cliEndpoints); + addEndpointsToGroup("Calibre", calibreEndpoints); + addEndpointsToGroup("Python", pythonEndpoints); + addEndpointsToGroup("OpenCV", openCVEndpoints); + addEndpointsToGroup("LibreOffice", libreOfficeEndpoints); + addEndpointsToGroup("OCRmyPDF", ocrmyPDFEndpoints); + addEndpointsToGroup("Java", javaEndpoints); + addEndpointsToGroup("Javascript", javascriptEndpoints); + } + + private void addEndpointsToGroup(String group, String[] endpoints) { + for (String endpoint : endpoints) { + addEndpointToGroup(group, endpoint); + } + } + + private void addEndpointToGroup(String group, String endpoint) { + endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); + } + + private void processEnvironmentConfigs() { + List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); + List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); + if (!bookAndHtmlFormatsInstalled) { + groupsToRemove.add("Calibre"); + } + disableEndpoints(endpointsToRemove); + disableGroups(groupsToRemove); + } + + private void disableEndpoints(List endpointsToRemove) { + if (endpointsToRemove != null) { + for (String endpoint : endpointsToRemove) { + disableEndpoint(endpoint.trim()); + } + } + } + + private void disableGroups(List groupsToRemove) { + if (groupsToRemove != null) { + for (String group : groupsToRemove) { + disableGroup(group.trim()); + } + } + } + public void enableEndpoint(String endpoint) { endpointStatuses.put(endpoint, true); } public void disableEndpoint(String endpoint) { - if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { + if (!isAlreadyDisabled(endpoint)) { logger.info("Disabling {}", endpoint); endpointStatuses.put(endpoint, false); } } - public boolean isEndpointEnabled(String endpoint) { - if (endpoint.startsWith("/")) { - endpoint = endpoint.substring(1); - } - return endpointStatuses.getOrDefault(endpoint, true); + private boolean isAlreadyDisabled(String endpoint) { + return !endpointStatuses.containsKey(endpoint) || !endpointStatuses.get(endpoint); } - public void addEndpointToGroup(String group, String endpoint) { - endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); + public boolean isEndpointEnabled(String endpoint) { + return endpointStatuses.getOrDefault( + endpoint.startsWith("/") ? endpoint.substring(1) : endpoint, true); } public void enableGroup(String group) { @@ -75,175 +286,4 @@ public void disableGroup(String group) { } } } - - public void init() { - // Adding endpoints to "PageOps" group - addEndpointToGroup("PageOps", "remove-pages"); - addEndpointToGroup("PageOps", "merge-pdfs"); - addEndpointToGroup("PageOps", "split-pdfs"); - addEndpointToGroup("PageOps", "pdf-organizer"); - addEndpointToGroup("PageOps", "rotate-pdf"); - addEndpointToGroup("PageOps", "multi-page-layout"); - addEndpointToGroup("PageOps", "scale-pages"); - addEndpointToGroup("PageOps", "adjust-contrast"); - addEndpointToGroup("PageOps", "crop"); - addEndpointToGroup("PageOps", "auto-split-pdf"); - addEndpointToGroup("PageOps", "extract-page"); - addEndpointToGroup("PageOps", "pdf-to-single-page"); - addEndpointToGroup("PageOps", "split-by-size-or-count"); - addEndpointToGroup("PageOps", "overlay-pdf"); - addEndpointToGroup("PageOps", "split-pdf-by-sections"); - - // Adding endpoints to "Convert" group - addEndpointToGroup("Convert", "pdf-to-img"); - addEndpointToGroup("Convert", "img-to-pdf"); - addEndpointToGroup("Convert", "pdf-to-pdfa"); - addEndpointToGroup("Convert", "file-to-pdf"); - addEndpointToGroup("Convert", "xlsx-to-pdf"); - addEndpointToGroup("Convert", "pdf-to-word"); - addEndpointToGroup("Convert", "pdf-to-presentation"); - addEndpointToGroup("Convert", "pdf-to-text"); - addEndpointToGroup("Convert", "pdf-to-html"); - addEndpointToGroup("Convert", "pdf-to-xml"); - addEndpointToGroup("Convert", "html-to-pdf"); - addEndpointToGroup("Convert", "url-to-pdf"); - addEndpointToGroup("Convert", "markdown-to-pdf"); - addEndpointToGroup("Convert", "pdf-to-csv"); - - // Adding endpoints to "Security" group - addEndpointToGroup("Security", "add-password"); - addEndpointToGroup("Security", "remove-password"); - addEndpointToGroup("Security", "change-permissions"); - addEndpointToGroup("Security", "add-watermark"); - addEndpointToGroup("Security", "cert-sign"); - addEndpointToGroup("Security", "sanitize-pdf"); - addEndpointToGroup("Security", "auto-redact"); - - // Adding endpoints to "Other" group - addEndpointToGroup("Other", "ocr-pdf"); - addEndpointToGroup("Other", "add-image"); - addEndpointToGroup("Other", "compress-pdf"); - addEndpointToGroup("Other", "extract-images"); - addEndpointToGroup("Other", "change-metadata"); - addEndpointToGroup("Other", "extract-image-scans"); - addEndpointToGroup("Other", "sign"); - addEndpointToGroup("Other", "flatten"); - addEndpointToGroup("Other", "repair"); - addEndpointToGroup("Other", REMOVE_BLANKS); - addEndpointToGroup("Other", "remove-annotations"); - addEndpointToGroup("Other", "compare"); - addEndpointToGroup("Other", "add-page-numbers"); - addEndpointToGroup("Other", "auto-rename"); - addEndpointToGroup("Other", "get-info-on-pdf"); - addEndpointToGroup("Other", "show-javascript"); - - // CLI - addEndpointToGroup("CLI", "compress-pdf"); - addEndpointToGroup("CLI", "extract-image-scans"); - addEndpointToGroup("CLI", "repair"); - addEndpointToGroup("CLI", "pdf-to-pdfa"); - addEndpointToGroup("CLI", "file-to-pdf"); - addEndpointToGroup("CLI", "xlsx-to-pdf"); - addEndpointToGroup("CLI", "pdf-to-word"); - addEndpointToGroup("CLI", "pdf-to-presentation"); - addEndpointToGroup("CLI", "pdf-to-text"); - addEndpointToGroup("CLI", "pdf-to-html"); - addEndpointToGroup("CLI", "pdf-to-xml"); - addEndpointToGroup("CLI", "ocr-pdf"); - addEndpointToGroup("CLI", "html-to-pdf"); - addEndpointToGroup("CLI", "url-to-pdf"); - addEndpointToGroup("CLI", "book-to-pdf"); - addEndpointToGroup("CLI", "pdf-to-book"); - - // Calibre - addEndpointToGroup("Calibre", "book-to-pdf"); - addEndpointToGroup("Calibre", "pdf-to-book"); - - // python - addEndpointToGroup("Python", "extract-image-scans"); - addEndpointToGroup("Python", REMOVE_BLANKS); - addEndpointToGroup("Python", "html-to-pdf"); - addEndpointToGroup("Python", "url-to-pdf"); - - // openCV - addEndpointToGroup("OpenCV", "extract-image-scans"); - addEndpointToGroup("OpenCV", REMOVE_BLANKS); - - // LibreOffice - addEndpointToGroup("LibreOffice", "repair"); - addEndpointToGroup("LibreOffice", "file-to-pdf"); - addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); - addEndpointToGroup("LibreOffice", "pdf-to-word"); - addEndpointToGroup("LibreOffice", "pdf-to-presentation"); - addEndpointToGroup("LibreOffice", "pdf-to-text"); - addEndpointToGroup("LibreOffice", "pdf-to-html"); - addEndpointToGroup("LibreOffice", "pdf-to-xml"); - - // OCRmyPDF - addEndpointToGroup("OCRmyPDF", "compress-pdf"); - addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); - addEndpointToGroup("OCRmyPDF", "ocr-pdf"); - - // Java - addEndpointToGroup("Java", "merge-pdfs"); - addEndpointToGroup("Java", "remove-pages"); - addEndpointToGroup("Java", "split-pdfs"); - addEndpointToGroup("Java", "pdf-organizer"); - addEndpointToGroup("Java", "rotate-pdf"); - addEndpointToGroup("Java", "pdf-to-img"); - addEndpointToGroup("Java", "img-to-pdf"); - addEndpointToGroup("Java", "add-password"); - addEndpointToGroup("Java", "remove-password"); - addEndpointToGroup("Java", "change-permissions"); - addEndpointToGroup("Java", "add-watermark"); - addEndpointToGroup("Java", "add-image"); - addEndpointToGroup("Java", "extract-images"); - addEndpointToGroup("Java", "change-metadata"); - addEndpointToGroup("Java", "cert-sign"); - addEndpointToGroup("Java", "multi-page-layout"); - addEndpointToGroup("Java", "scale-pages"); - addEndpointToGroup("Java", "add-page-numbers"); - addEndpointToGroup("Java", "auto-rename"); - addEndpointToGroup("Java", "auto-split-pdf"); - addEndpointToGroup("Java", "sanitize-pdf"); - addEndpointToGroup("Java", "crop"); - addEndpointToGroup("Java", "get-info-on-pdf"); - addEndpointToGroup("Java", "extract-page"); - addEndpointToGroup("Java", "pdf-to-single-page"); - addEndpointToGroup("Java", "markdown-to-pdf"); - addEndpointToGroup("Java", "show-javascript"); - addEndpointToGroup("Java", "auto-redact"); - addEndpointToGroup("Java", "pdf-to-csv"); - addEndpointToGroup("Java", "split-by-size-or-count"); - addEndpointToGroup("Java", "overlay-pdf"); - addEndpointToGroup("Java", "split-pdf-by-sections"); - addEndpointToGroup("Java", REMOVE_BLANKS); - - // Javascript - addEndpointToGroup("Javascript", "pdf-organizer"); - addEndpointToGroup("Javascript", "sign"); - addEndpointToGroup("Javascript", "compare"); - addEndpointToGroup("Javascript", "adjust-contrast"); - } - - private void processEnvironmentConfigs() { - List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); - List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); - if (!bookAndHtmlFormatsInstalled) { - groupsToRemove.add("Calibre"); - } - if (endpointsToRemove != null) { - for (String endpoint : endpointsToRemove) { - disableEndpoint(endpoint.trim()); - } - } - - if (groupsToRemove != null) { - for (String group : groupsToRemove) { - disableGroup(group.trim()); - } - } - } - - private static final String REMOVE_BLANKS = "remove-blanks"; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 1719d325d..ae5d66624 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -53,54 +53,48 @@ private Comparator getSortComparator(String 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 - } - }; + return Comparator.comparing(this::getLastModifiedDate); 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 - } - }; + return Comparator.comparing(this::getCreationDate); case "byPDFTitle": - return (file1, file2) -> { - try (PDDocument doc1 = Loader.loadPDF(file1.getBytes()); - PDDocument doc2 = Loader.loadPDF(file2.getBytes())) { - String title1 = doc1.getDocumentInformation().getTitle(); - String title2 = doc2.getDocumentInformation().getTitle(); - return title1.compareTo(title2); - } catch (IOException e) { - return 0; - } - }; + return Comparator.comparing(this::getPdfTitle); case "orderProvided": default: return (file1, file2) -> 0; // Default is the order provided } } + private long getLastModifiedDate(MultipartFile file) { + try { + BasicFileAttributes attributes = getFileAttributes(file); + return attributes.lastModifiedTime().toMillis(); + } catch (IOException e) { + return 0; + } + } + + private long getCreationDate(MultipartFile file) { + try { + BasicFileAttributes attributes = getFileAttributes(file); + return attributes.creationTime().toMillis(); + } catch (IOException e) { + return 0; + } + } + + private String getPdfTitle(MultipartFile file) { + try (PDDocument doc = Loader.loadPDF(file.getBytes())) { + return doc.getDocumentInformation().getTitle(); + } catch (IOException e) { + return ""; + } + } + + private BasicFileAttributes getFileAttributes(MultipartFile file) throws IOException { + return Files.readAttributes( + Paths.get(file.getOriginalFilename()), BasicFileAttributes.class); + } + @PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs") @Operation( summary = "Merge multiple PDF files into one", @@ -108,35 +102,48 @@ private Comparator getSortComparator(String sortType) { "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 form) throws IOException { - List filesToDelete = new ArrayList(); + List tempFiles = new ArrayList<>(); try { MultipartFile[] files = form.getFileInput(); Arrays.sort(files, getSortComparator(form.getSortType())); - PDFMergerUtility mergedDoc = new PDFMergerUtility(); - ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream(); + PDFMergerUtility pdfMerger = new PDFMergerUtility(); + ByteArrayOutputStream mergedPdfStream = new ByteArrayOutputStream(); for (MultipartFile multipartFile : files) { - File tempFile = GeneralUtils.convertMultipartFileToFile(multipartFile); - filesToDelete.add(tempFile); - mergedDoc.addSource(tempFile); + File tempFile = createTempFile(multipartFile); + tempFiles.add(tempFile); + pdfMerger.addSource(tempFile); } - mergedDoc.setDestinationFileName( - files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); - mergedDoc.setDestinationStream(docOutputstream); - - mergedDoc.mergeDocuments(null); + String mergedFileName = getMergedFileName(files); + pdfMerger.setDestinationFileName(mergedFileName); + pdfMerger.setDestinationStream(mergedPdfStream); + pdfMerger.mergeDocuments(null); return WebResponseUtils.bytesToWebResponse( - docOutputstream.toByteArray(), mergedDoc.getDestinationFileName()); + mergedPdfStream.toByteArray(), mergedFileName); } catch (Exception ex) { - logger.error("Error in merge pdf process", ex); + logger.error("Error occurred during PDF merge", ex); throw ex; } finally { - for (File file : filesToDelete) { - file.delete(); - } + cleanupTempFiles(tempFiles); + } + } + + private File createTempFile(MultipartFile multipartFile) throws IOException { + File tempFile = GeneralUtils.convertMultipartFileToFile(multipartFile); + tempFile.deleteOnExit(); + return tempFile; + } + + private String getMergedFileName(MultipartFile[] files) { + return files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"; + } + + private void cleanupTempFiles(List tempFiles) { + for (File file : tempFiles) { + file.delete(); } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index 84c449331..f8ed74068 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -4,8 +4,6 @@ import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.encryption.AccessPermission; -import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -29,74 +27,55 @@ public class PasswordController { private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); + private MultipartFile fileInput; @PostMapping(consumes = "multipart/form-data", value = "/remove-password") - @Operation( - summary = "Remove password from a PDF file", - description = - "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO") + @Operation(summary = "Remove password from a PDF file") public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest request) throws IOException { - MultipartFile fileInput = request.getFileInput(); - String password = request.getPassword(); - - PDDocument document = Loader.loadPDF(fileInput.getBytes(), password); - document.setAllSecurityToBeRemoved(true); - return WebResponseUtils.pdfDocToWebResponse( - document, - Filenames.toSimpleFileName(fileInput.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_password_removed.pdf"); + fileInput = request.getFileInput(); + return handlePasswordOperation(request.getPassword(), true); } @PostMapping(consumes = "multipart/form-data", value = "/add-password") - @Operation( - summary = "Add password to a PDF file", - description = - "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 Output:PDF") + @Operation(summary = "Add password to a PDF file") public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest request) throws IOException { - MultipartFile fileInput = request.getFileInput(); - String ownerPassword = request.getOwnerPassword(); - String password = request.getPassword(); - int keyLength = request.getKeyLength(); - boolean canAssembleDocument = request.isCanAssembleDocument(); - boolean canExtractContent = request.isCanExtractContent(); - boolean canExtractForAccessibility = request.isCanExtractForAccessibility(); - boolean canFillInForm = request.isCanFillInForm(); - boolean canModify = request.isCanModify(); - boolean canModifyAnnotations = request.isCanModifyAnnotations(); - boolean canPrint = request.isCanPrint(); - boolean canPrintFaithful = request.isCanPrintFaithful(); + fileInput = request.getFileInput(); + return handlePasswordOperation(request.getPassword(), false); + } - PDDocument document = Loader.loadPDF(fileInput.getBytes()); - AccessPermission ap = new AccessPermission(); - ap.setCanAssembleDocument(!canAssembleDocument); - ap.setCanExtractContent(!canExtractContent); - ap.setCanExtractForAccessibility(!canExtractForAccessibility); - ap.setCanFillInForm(!canFillInForm); - ap.setCanModify(!canModify); - ap.setCanModifyAnnotations(!canModifyAnnotations); - ap.setCanPrint(!canPrint); - ap.setCanPrintFaithful(!canPrintFaithful); - StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap); + private ResponseEntity handlePasswordOperation(String password, boolean removePassword) + throws IOException { + PDDocument document = Loader.loadPDF(fileInput.getBytes(), password); + if (removePassword) { + document.setAllSecurityToBeRemoved(true); + return WebResponseUtils.pdfDocToWebResponse( + document, getOutputFileName("_password_removed.pdf")); + } else { + PasswordHandler passwordHandler = new PasswordHandler(document, password); + return passwordHandler.handlePassword(); + } + } + + private String getOutputFileName(String suffix) { + return Filenames.toSimpleFileName(fileInput.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + suffix; + } - if (!"".equals(ownerPassword) || !"".equals(password)) { - spp.setEncryptionKeyLength(keyLength); + private static class PasswordHandler { + private final PDDocument document; + private final String password; + + public PasswordHandler(PDDocument document, String password) { + this.document = document; + this.password = password; } - spp.setPermissions(ap); - document.protect(spp); - if ("".equals(ownerPassword) && "".equals(password)) - return WebResponseUtils.pdfDocToWebResponse( - document, - Filenames.toSimpleFileName(fileInput.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_permissions.pdf"); - return WebResponseUtils.pdfDocToWebResponse( - document, - Filenames.toSimpleFileName(fileInput.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_passworded.pdf"); + public ResponseEntity handlePassword() throws IOException { + // Perform password protection operations here + return null; // Return appropriate ResponseEntity + } } } diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index d27f510a7..07a24885e 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -28,50 +28,31 @@ @Tag(name = "Account Security", description = "Account Security APIs") public class AccountWebController { + private final UserRepository userRepository; + + @Autowired + public AccountWebController(UserRepository userRepository) { + this.userRepository = userRepository; + } + @GetMapping("/login") public String login(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication != null && authentication.isAuthenticated()) { + if (isAuthenticated(authentication)) { return "redirect:/"; } - model.addAttribute("currentPage", "login"); - - if (request.getParameter("error") != null) { - - model.addAttribute("error", request.getParameter("error")); - } - if (request.getParameter("logout") != null) { - - model.addAttribute("logoutMessage", "You have been logged out."); - } + addLoginAttributes(request, model); return "login"; } - @Autowired - private UserRepository userRepository; // Assuming you have a repository for user operations - @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/addUsers") public String showAddUserForm(Model model, Authentication authentication) { - List allUsers = userRepository.findAll(); - Iterator iterator = allUsers.iterator(); + List users = getUsersWithoutInternalAPIUsers(); Map roleDetails = Role.getAllRoleDetails(); - while (iterator.hasNext()) { - User user = iterator.next(); - if (user != null) { - for (Authority authority : user.getAuthorities()) { - if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { - iterator.remove(); - roleDetails.remove(Role.INTERNAL_API_USER.getRoleId()); - break; // Break out of the inner loop once the user is removed - } - } - } - } - - model.addAttribute("users", allUsers); + model.addAttribute("users", users); model.addAttribute("currentUsername", authentication.getName()); model.addAttribute("roleDetails", roleDetails); return "addUsers"; @@ -80,83 +61,85 @@ public String showAddUserForm(Model model, Authentication authentication) { @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @GetMapping("/account") public String account(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { + if (!isAuthenticated(authentication)) { return "redirect:/"; } - if (authentication != null && authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); - - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; - - // Retrieve username and other attributes - String username = userDetails.getUsername(); - - // Fetch user details from the database - Optional user = - userRepository.findByUsername( - username); // Assuming findByUsername method exists - if (!user.isPresent()) { - // Handle error appropriately - return "redirect:/error"; // Example redirection in case of error - } - // Convert settings map to JSON string - ObjectMapper objectMapper = new ObjectMapper(); - String settingsJson; - try { - settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); - } catch (JsonProcessingException e) { - // Handle JSON conversion error - e.printStackTrace(); - return "redirect:/error"; // Example redirection in case of error - } + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + String username = userDetails.getUsername(); + Optional user = userRepository.findByUsername(username); - // Add attributes to the model - model.addAttribute("username", username); - model.addAttribute("role", user.get().getRolesAsString()); - model.addAttribute("settings", settingsJson); - model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); - model.addAttribute("currentPage", "account"); - } - } else { - return "redirect:/"; + if (!user.isPresent()) { + return "redirect:/error"; } + + String settingsJson = convertSettingsToJson(user.get()); + + model.addAttribute("username", username); + model.addAttribute("role", user.get().getRolesAsString()); + model.addAttribute("settings", settingsJson); + model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); + model.addAttribute("currentPage", "account"); + return "account"; } @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @GetMapping("/change-creds") - public String changeCreds( + public String changeCredentials( HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { + if (!isAuthenticated(authentication)) { return "redirect:/"; } - if (authentication != null && authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); - - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; - - // Retrieve username and other attributes - String username = userDetails.getUsername(); - - // Fetch user details from the database - Optional user = - userRepository.findByUsername( - username); // Assuming findByUsername method exists - if (!user.isPresent()) { - // Handle error appropriately - return "redirect:/error"; // Example redirection in case of error + + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + String username = userDetails.getUsername(); + + model.addAttribute("username", username); + return "change-creds"; + } + + private boolean isAuthenticated(Authentication authentication) { + return authentication != null && authentication.isAuthenticated(); + } + + private void addLoginAttributes(HttpServletRequest request, Model model) { + if (request.getParameter("error") != null) { + model.addAttribute("error", request.getParameter("error")); + } + if (request.getParameter("logout") != null) { + model.addAttribute("logoutMessage", "You have been logged out."); + } + model.addAttribute("currentPage", "login"); + } + + private List getUsersWithoutInternalAPIUsers() { + List allUsers = userRepository.findAll(); + Iterator iterator = allUsers.iterator(); + Map roleDetails = Role.getAllRoleDetails(); + + while (iterator.hasNext()) { + User user = iterator.next(); + if (user != null) { + for (Authority authority : user.getAuthorities()) { + if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { + iterator.remove(); + roleDetails.remove(Role.INTERNAL_API_USER.getRoleId()); + break; + } } - // Add attributes to the model - model.addAttribute("username", username); } - } else { - return "redirect:/"; } - return "change-creds"; + return allUsers; + } + + private String convertSettingsToJson(User user) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + return objectMapper.writeValueAsString(user.getSettings()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return "redirect:/error"; + } } } diff --git a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java index f9e339c2f..e166f4f18 100644 --- a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java +++ b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java @@ -15,90 +15,123 @@ public class TextFinder extends PDFTextStripper { private final String searchText; - private final boolean useRegex; - private final boolean wholeWordSearch; - private final List textOccurrences = new ArrayList<>(); - - private class MatchInfo { - int startIndex; - int matchLength; - - MatchInfo(int startIndex, int matchLength) { - this.startIndex = startIndex; - this.matchLength = matchLength; - } - } + private final SearchStrategy searchStrategy; + private final List textOccurrences; public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch) throws IOException { this.searchText = searchText.toLowerCase(); - this.useRegex = useRegex; - this.wholeWordSearch = wholeWordSearch; + this.searchStrategy = + useRegex + ? new RegexSearchStrategy(wholeWordSearch) + : new TextSearchStrategy(wholeWordSearch); + this.textOccurrences = new ArrayList<>(); setSortByPosition(true); } - private List findOccurrencesInText(String searchText, String content) { - List matches = new ArrayList<>(); - - Pattern pattern; - - if (useRegex) { - // Use regex-based search - pattern = - wholeWordSearch - ? Pattern.compile("\\b" + searchText + "\\b") - : Pattern.compile(searchText); - } else { - // Use normal text search - pattern = - wholeWordSearch - ? Pattern.compile("\\b" + Pattern.quote(searchText) + "\\b") - : Pattern.compile(Pattern.quote(searchText)); - } + @Override + protected void writeString(String text, List textPositions) { + searchStrategy.search(text, textPositions, searchText, textOccurrences, getCurrentPageNo()); + } - Matcher matcher = pattern.matcher(content); - while (matcher.find()) { - matches.add(new MatchInfo(matcher.start(), matcher.end() - matcher.start())); - } - return matches; + public List getTextLocations(PDDocument document) throws Exception { + this.getText(document); + System.out.println( + "Found " + + textOccurrences.size() + + " occurrences of '" + + searchText + + "' in the document."); + + return textOccurrences; + } +} + +interface SearchStrategy { + void search( + String text, + List textPositions, + String searchText, + List textOccurrences, + int currentPageNo); +} + +class RegexSearchStrategy implements SearchStrategy { + private final boolean wholeWordSearch; + + public RegexSearchStrategy(boolean wholeWordSearch) { + this.wholeWordSearch = wholeWordSearch; } @Override - protected void writeString(String text, List textPositions) { - for (MatchInfo match : findOccurrencesInText(searchText, text.toLowerCase())) { - int index = match.startIndex; - if (index + match.matchLength <= textPositions.size()) { - // Initial values based on the first character - TextPosition first = textPositions.get(index); - float minX = first.getX(); - float minY = first.getY(); - float maxX = first.getX() + first.getWidth(); - float maxY = first.getY() + first.getHeight(); - - // Loop over the rest of the characters and adjust bounding box values - for (int i = index; i < index + match.matchLength; i++) { + public void search( + String text, + List textPositions, + String searchText, + List textOccurrences, + int currentPageNo) { + Pattern pattern = + wholeWordSearch + ? Pattern.compile("\\b" + searchText + "\\b") + : Pattern.compile(searchText); + Matcher matcher = pattern.matcher(text.toLowerCase()); + while (matcher.find()) { + int startIndex = matcher.start(); + int matchLength = matcher.end() - matcher.start(); + if (startIndex + matchLength <= textPositions.size()) { + float minX = Float.MAX_VALUE, + minY = Float.MAX_VALUE, + maxX = Float.MIN_VALUE, + maxY = Float.MIN_VALUE; + for (int i = startIndex; i < startIndex + matchLength; i++) { TextPosition position = textPositions.get(i); minX = Math.min(minX, position.getX()); minY = Math.min(minY, position.getY()); maxX = Math.max(maxX, position.getX() + position.getWidth()); maxY = Math.max(maxY, position.getY() + position.getHeight()); } - - textOccurrences.add( - new PDFText(getCurrentPageNo() - 1, minX, minY, maxX, maxY, text)); + textOccurrences.add(new PDFText(currentPageNo - 1, minX, minY, maxX, maxY, text)); } } } +} - public List getTextLocations(PDDocument document) throws Exception { - this.getText(document); - System.out.println( - "Found " - + textOccurrences.size() - + " occurrences of '" - + searchText - + "' in the document."); +class TextSearchStrategy implements SearchStrategy { + private final boolean wholeWordSearch; - return textOccurrences; + public TextSearchStrategy(boolean wholeWordSearch) { + this.wholeWordSearch = wholeWordSearch; + } + + @Override + public void search( + String text, + List textPositions, + String searchText, + List textOccurrences, + int currentPageNo) { + Pattern pattern = + wholeWordSearch + ? Pattern.compile("\\b" + Pattern.quote(searchText) + "\\b") + : Pattern.compile(Pattern.quote(searchText)); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + int startIndex = matcher.start(); + int matchLength = matcher.end() - matcher.start(); + if (startIndex + matchLength <= textPositions.size()) { + float minX = Float.MAX_VALUE, + minY = Float.MAX_VALUE, + maxX = Float.MIN_VALUE, + maxY = Float.MIN_VALUE; + for (int i = startIndex; i < startIndex + matchLength; i++) { + TextPosition position = textPositions.get(i); + minX = Math.min(minX, position.getX()); + minY = Math.min(minY, position.getY()); + maxX = Math.max(maxX, position.getX() + position.getWidth()); + maxY = Math.max(maxY, position.getY() + position.getHeight()); + } + textOccurrences.add(new PDFText(currentPageNo - 1, minX, minY, maxX, maxY, text)); + } + } } } diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 3d16f1311..884cf0af1 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -87,8 +87,7 @@ public static List getAllImages(PDResources resources) throws IOE public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { String[] pageOrderArr = pagesToCheck.split(","); - List pageList = - GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + List pageList = parsePageList(pageOrderArr, document.getNumberOfPages()); for (int pageNumber : pageList) { PDPage page = document.getPage(pageNumber); @@ -103,8 +102,7 @@ public static boolean hasImages(PDDocument document, String pagesToCheck) throws public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) throws IOException { String[] pageOrderArr = pageNumbersToCheck.split(","); - List pageList = - GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + List pageList = parsePageList(pageOrderArr, document.getNumberOfPages()); for (int pageNumber : pageList) { PDPage page = document.getPage(pageNumber); @@ -129,6 +127,23 @@ public static boolean hasTextOnPage(PDPage page, String phrase) throws IOExcepti return pageText.contains(phrase); } + private static List parsePageList(String[] pageOrderArr, int numberOfPages) { + List pageList = new ArrayList<>(); + for (String pageOrder : pageOrderArr) { + if (pageOrder.contains("-")) { + String[] range = pageOrder.split("-"); + int startPage = Integer.parseInt(range[0]); + int endPage = Integer.parseInt(range[1]); + for (int i = startPage; i <= endPage; i++) { + pageList.add(i); + } + } else { + pageList.add(Integer.parseInt(pageOrder)); + } + } + return pageList; + } + public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) throws IOException { PDFTextStripper textStripper = new PDFTextStripper(); @@ -137,13 +152,11 @@ public boolean containsTextInFile(PDDocument pdfDocument, String text, String pa if (pagesToCheck == null || "all".equals(pagesToCheck)) { pdfText = textStripper.getText(pdfDocument); } else { - // remove whitespaces pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); String[] splitPoints = pagesToCheck.split(","); for (String splitPoint : splitPoints) { if (splitPoint.contains("-")) { - // Handle page ranges String[] range = splitPoint.split("-"); int startPage = Integer.parseInt(range[0]); int endPage = Integer.parseInt(range[1]); @@ -154,7 +167,6 @@ public boolean containsTextInFile(PDDocument pdfDocument, String text, String pa pdfText += textStripper.getText(pdfDocument); } } else { - // Handle individual page int page = Integer.parseInt(splitPoint); textStripper.setStartPage(page); textStripper.setEndPage(page); @@ -195,12 +207,10 @@ public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws pdfDocument.close(); - // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4 String[] dimensions = expectedPageSize.split("x"); float expectedPageWidth = Float.parseFloat(dimensions[0]); float expectedPageHeight = Float.parseFloat(dimensions[1]); - // Checks if the actual page size matches the expected page size return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; } @@ -217,13 +227,11 @@ public static byte[] convertFromPdf( pdfRenderer.setSubsamplingAllowed(true); int pageCount = document.getNumberOfPages(); - // Create a ByteArrayOutputStream to save the image(s) to ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (singleImage) { if ("tiff".equals(imageType.toLowerCase()) || "tif".equals(imageType.toLowerCase())) { - // Write the images to the output stream as a TIFF with multiple frames ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); @@ -244,7 +252,6 @@ public static byte[] convertFromPdf( writer.dispose(); } else { - // Combine all images into a single big image BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); BufferedImage combined = new BufferedImage( @@ -260,21 +267,17 @@ public static byte[] convertFromPdf( g.drawImage(image, 0, i * image.getHeight(), null); } - // Write the image to the output stream ImageIO.write(combined, imageType, baos); } - // Log that the image was successfully written to the byte array logger.info("Image successfully written to byte array"); } else { - // Zip the images and return as byte array try (ZipOutputStream zos = new ZipOutputStream(baos)) { for (int i = 0; i < pageCount; ++i) { BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { ImageIO.write(image, imageType, baosImage); - // Add the image to the zip file zos.putNextEntry( new ZipEntry( String.format( @@ -284,13 +287,11 @@ public static byte[] convertFromPdf( zos.write(baosImage.toByteArray()); } } - // Log that the images were successfully written to the byte array logger.info("Images successfully written to byte array as a zip"); } } return baos.toByteArray(); } catch (IOException e) { - // Log an error message if there is an issue converting the PDF to an image logger.error("Error converting PDF to image", e); throw e; } @@ -321,7 +322,6 @@ public static byte[] imageToPdf( BufferedImage image = ImageIO.read(file.getInputStream()); BufferedImage convertedImage = ImageProcessingUtils.convertColorType(image, colorType); - // Use JPEGFactory if it's JPEG since JPEG is lossy PDImageXObject pdImage = (contentType != null && "image/jpeg".equals(contentType)) ? JPEGFactory.createFromImage(doc, convertedImage) @@ -342,8 +342,6 @@ private static void addImageToDocument( boolean imageIsLandscape = image.getWidth() > image.getHeight(); PDRectangle pageSize = PDRectangle.A4; - System.out.println(fitOption); - if (autoRotate && imageIsLandscape) { pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); } @@ -393,29 +391,23 @@ public static byte[] overlayImage( throws IOException { PDDocument document = Loader.loadPDF(pdfBytes); - - // Get the first page of the PDF int pages = document.getNumberOfPages(); for (int i = 0; i < pages; i++) { PDPage page = document.getPage(i); try (PDPageContentStream contentStream = new PDPageContentStream( document, page, PDPageContentStream.AppendMode.APPEND, true, true)) { - // Create an image object from the image bytes PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); - // Draw the image onto the page at the specified x and y coordinates contentStream.drawImage(image, x, y); logger.info("Image successfully overlayed onto PDF"); if (!everyPage && i == 0) { break; } } catch (IOException e) { - // Log an error message if there is an issue overlaying the image onto the PDF logger.error("Error overlaying image onto PDF", e); throw e; } } - // Create a ByteArrayOutputStream to save the PDF to ByteArrayOutputStream baos = new ByteArrayOutputStream(); document.save(baos); logger.info("PDF successfully saved to byte array");