# Description of Changes This pull request includes several changes primarily focused on improving configuration management, removing deprecated methods, and updating paths for external dependencies. The most important changes are summarized below: ### Configuration Management Improvements: * Added a new `RuntimePathConfig` class to manage dynamic paths for operations and pipeline configurations (`src/main/java/stirling/software/SPDF/config/RuntimePathConfig.java`). * Removed the `bookAndHtmlFormatsInstalled` bean and its associated logic from `AppConfig` and `EndpointConfiguration` (`src/main/java/stirling/software/SPDF/config/AppConfig.java`, `src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`). [[1]](diffhunk://#diff-4d774ec79aa55750c0a4739bee971b68877078b73654e863fd40ee924347e143L130-L138) [[2]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL12-L35) [[3]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL275-L280) ### External Dependency Path Updates: * Updated paths for `weasyprint` and `unoconvert` in `ExternalAppDepConfig` to use values from `RuntimePathConfig` (`src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java`). [[1]](diffhunk://#diff-c47af298c07c2622aa98b038b78822c56bdb002de71081e102d344794e7832a6R12-L33) [[2]](diffhunk://#diff-c47af298c07c2622aa98b038b78822c56bdb002de71081e102d344794e7832a6L104-R115) ### Minor Adjustments: * Corrected a typo from "Unoconv" to "Unoconvert" in `EndpointConfiguration` (`src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`). --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
201 lines
8.7 KiB
Java
201 lines
8.7 KiB
Java
package stirling.software.SPDF.controller.api;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Paths;
|
|
import java.nio.file.attribute.BasicFileAttributes;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
|
|
import org.apache.pdfbox.Loader;
|
|
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
import org.apache.pdfbox.pdmodel.PDPage;
|
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
|
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
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 io.swagger.v3.oas.annotations.Operation;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
|
import stirling.software.SPDF.utils.GeneralUtils;
|
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
|
|
@RestController
|
|
@Slf4j
|
|
@RequestMapping("/api/v1/general")
|
|
@Tag(name = "General", description = "General APIs")
|
|
public class MergeController {
|
|
|
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
|
|
|
@Autowired
|
|
public MergeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
|
}
|
|
|
|
// Merges a list of PDDocument objects into a single PDDocument
|
|
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
|
PDDocument mergedDoc = pdfDocumentFactory.createNewDocument();
|
|
for (PDDocument doc : documents) {
|
|
for (PDPage page : doc.getPages()) {
|
|
mergedDoc.addPage(page);
|
|
}
|
|
}
|
|
return mergedDoc;
|
|
}
|
|
|
|
// Returns a comparator for sorting MultipartFile arrays based on the given sort type
|
|
private Comparator<MultipartFile> 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 = 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;
|
|
}
|
|
};
|
|
case "orderProvided":
|
|
default:
|
|
return (file1, file2) -> 0; // Default is the order provided
|
|
}
|
|
}
|
|
|
|
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
|
@Operation(
|
|
summary = "Merge multiple PDF files into one",
|
|
description =
|
|
"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<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
|
|
throws IOException {
|
|
List<File> filesToDelete = new ArrayList<>(); // List of temporary files to delete
|
|
ByteArrayOutputStream docOutputstream =
|
|
new ByteArrayOutputStream(); // Stream for the merged document
|
|
PDDocument mergedDocument = null;
|
|
|
|
boolean removeCertSign = form.isRemoveCertSign();
|
|
|
|
try {
|
|
MultipartFile[] files = form.getFileInput();
|
|
Arrays.sort(
|
|
files,
|
|
getSortComparator(
|
|
form.getSortType())); // Sort files based on the given sort type
|
|
|
|
PDFMergerUtility mergerUtility = new PDFMergerUtility();
|
|
for (MultipartFile multipartFile : files) {
|
|
File tempFile =
|
|
GeneralUtils.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
|
|
}
|
|
mergerUtility.setDestinationStream(
|
|
docOutputstream); // Set the output stream for the merged document
|
|
mergerUtility.mergeDocuments(null); // Merge the documents
|
|
|
|
byte[] mergedPdfBytes = docOutputstream.toByteArray(); // Get merged document bytes
|
|
|
|
// Load the merged PDF document
|
|
mergedDocument = Loader.loadPDF(mergedPdfBytes);
|
|
|
|
// Remove signatures if removeCertSign is true
|
|
if (removeCertSign) {
|
|
PDDocumentCatalog catalog = mergedDocument.getDocumentCatalog();
|
|
PDAcroForm acroForm = catalog.getAcroForm();
|
|
if (acroForm != null) {
|
|
List<PDField> fieldsToRemove =
|
|
acroForm.getFields().stream()
|
|
.filter(field -> field instanceof PDSignatureField)
|
|
.collect(Collectors.toList());
|
|
|
|
if (!fieldsToRemove.isEmpty()) {
|
|
acroForm.flatten(
|
|
fieldsToRemove,
|
|
false); // Flatten the fields, effectively removing them
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the modified document to a new ByteArrayOutputStream
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
mergedDocument.save(baos);
|
|
|
|
String mergedFileName =
|
|
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
|
+ "_merged_unsigned.pdf";
|
|
return WebResponseUtils.bytesToWebResponse(
|
|
baos.toByteArray(), mergedFileName); // Return the modified PDF
|
|
|
|
} catch (Exception ex) {
|
|
log.error("Error in merge pdf process", ex);
|
|
throw ex;
|
|
} finally {
|
|
for (File file : filesToDelete) {
|
|
if (file != null) {
|
|
Files.deleteIfExists(file.toPath()); // Delete temporary files
|
|
}
|
|
}
|
|
docOutputstream.close();
|
|
if (mergedDocument != null) {
|
|
mergedDocument.close(); // Close the merged document
|
|
}
|
|
}
|
|
}
|
|
}
|