Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

193 lines
8.5 KiB
Java
Raw Normal View History

package stirling.software.SPDF.controller.api;
2023-12-25 23:27:08 +02:00
import java.io.ByteArrayOutputStream;
2024-01-12 23:15:27 +00:00
import java.io.File;
2023-12-25 23:27:08 +02:00
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
2024-01-12 23:15:27 +00:00
import java.util.ArrayList;
2023-12-25 23:27:08 +02:00
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
2023-12-30 19:11:27 +00:00
2024-01-12 23:15:27 +00:00
import org.apache.pdfbox.Loader;
2023-12-25 22:36:08 +02:00
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
2023-09-09 00:25:27 +01:00
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
2023-09-11 23:19:50 +01:00
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
2023-12-30 19:11:27 +00:00
2023-12-25 23:27:08 +02:00
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
2023-12-30 19:11:27 +00:00
2023-09-09 00:25:27 +01:00
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
2024-01-12 23:15:27 +00:00
import stirling.software.SPDF.utils.GeneralUtils;
2023-05-31 20:15:48 +01:00
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
2023-09-11 23:19:50 +01:00
@RequestMapping("/api/v1/general")
2023-06-25 09:16:32 +01:00
@Tag(name = "General", description = "General APIs")
public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
// Merges a list of PDDocument objects into a single PDDocument
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
2023-12-25 22:36:08 +02:00
PDDocument mergedDoc = new PDDocument();
for (PDDocument doc : documents) {
for (PDPage page : doc.getPages()) {
mergedDoc.addPage(page);
}
}
2023-12-25 22:36:08 +02:00
return mergedDoc;
2023-08-17 22:03:36 +01:00
}
// Returns a comparator for sorting MultipartFile arrays based on the given sort type
2023-12-25 22:36:08 +02:00
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) -> {
2024-01-12 23:15:27 +00:00
try (PDDocument doc1 = Loader.loadPDF(file1.getBytes());
PDDocument doc2 = Loader.loadPDF(file2.getBytes())) {
2023-12-25 22:36:08 +02:00
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
}
}
2023-12-25 22:36:08 +02:00
@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();
2023-12-25 22:36:08 +02:00
try {
MultipartFile[] files = form.getFileInput();
Arrays.sort(
files,
getSortComparator(
form.getSortType())); // Sort files based on the given sort type
PDFMergerUtility mergerUtility = new PDFMergerUtility();
2024-01-12 23:15:27 +00:00
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
2023-08-17 22:03:36 +01:00
}
mergerUtility.setDestinationStream(
docOutputstream); // Set the output stream for the merged document
mergerUtility.mergeDocuments(null); // Merge the documents
2023-12-25 22:36:08 +02:00
byte[] mergedPdfBytes = docOutputstream.toByteArray(); // Get merged document bytes
2024-01-12 23:15:27 +00:00
// Load the merged PDF document
mergedDocument = Loader.loadPDF(mergedPdfBytes);
2023-12-25 22:36:08 +02:00
// 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";
2023-12-25 23:27:08 +02:00
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), mergedFileName); // Return the modified PDF
2023-12-25 22:36:08 +02:00
} catch (Exception ex) {
logger.error("Error in merge pdf process", ex);
throw ex;
2024-01-12 23:15:27 +00:00
} finally {
for (File file : filesToDelete) {
2024-05-27 16:31:00 +01:00
if (file != null) {
Files.deleteIfExists(file.toPath()); // Delete temporary files
2024-05-27 16:31:00 +01:00
}
2024-01-12 23:15:27 +00:00
}
docOutputstream.close();
if (mergedDocument != null) {
mergedDocument.close(); // Close the merged document
}
2023-08-17 22:03:36 +01:00
}
}
2023-08-17 22:03:36 +01:00
}