2023-04-28 23:18:10 +01:00
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 ;
2024-06-09 14:58:05 +02:00
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 ;
2023-04-28 23:18:10 +01:00
import org.apache.pdfbox.pdmodel.PDDocument ;
2024-06-09 14:58:05 +02:00
import org.apache.pdfbox.pdmodel.PDDocumentCatalog ;
2023-04-28 23:18:10 +01:00
import org.apache.pdfbox.pdmodel.PDPage ;
2024-06-09 14:58:05 +02:00
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm ;
import org.apache.pdfbox.pdmodel.interactive.form.PDField ;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField ;
2023-04-28 23:18:10 +01:00
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 ;
2023-04-28 23:18:10 +01:00
import org.springframework.web.bind.annotation.PostMapping ;
2023-09-11 23:19:50 +01:00
import org.springframework.web.bind.annotation.RequestMapping ;
2023-04-28 23:18:10 +01:00
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 ;
2023-04-28 23:18:10 +01:00
@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 " )
2023-04-28 23:18:10 +01:00
public class MergeController {
private static final Logger logger = LoggerFactory . getLogger ( MergeController . class ) ;
2024-06-09 14:58:05 +02:00
// Merges a list of PDDocument objects into a single PDDocument
2024-02-25 04:26:35 +08:00
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-04-28 23:18:10 +01:00
}
2023-12-25 22:36:08 +02:00
return mergedDoc ;
2023-08-17 22:03:36 +01:00
}
2023-04-28 23:18:10 +01:00
2024-06-09 14:58:05 +02: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-04-28 23:18:10 +01:00
}
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 {
2024-06-09 14:58:05 +02:00
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 ( ) ;
2024-06-09 14:58:05 +02:00
Arrays . sort (
files ,
getSortComparator (
form . getSortType ( ) ) ) ; // Sort files based on the given sort type
2023-04-28 23:18:10 +01:00
2024-06-09 14:58:05 +02:00
PDFMergerUtility mergerUtility = new PDFMergerUtility ( ) ;
2024-01-12 23:15:27 +00:00
for ( MultipartFile multipartFile : files ) {
2024-06-09 14:58:05 +02:00
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
}
2024-06-09 14:58:05 +02: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
2024-06-09 14:58:05 +02:00
byte [ ] mergedPdfBytes = docOutputstream . toByteArray ( ) ; // Get merged document bytes
2024-01-12 23:15:27 +00:00
2024-06-09 14:58:05 +02:00
// Load the merged PDF document
mergedDocument = Loader . loadPDF ( mergedPdfBytes ) ;
2023-12-25 22:36:08 +02:00
2024-06-09 14:58:05 +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 (
2024-06-09 14:58:05 +02:00
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 ) {
2024-06-09 14:58:05 +02:00
Files . deleteIfExists ( file . toPath ( ) ) ; // Delete temporary files
2024-05-27 16:31:00 +01:00
}
2024-01-12 23:15:27 +00:00
}
2024-06-09 14:58:05 +02:00
docOutputstream . close ( ) ;
if ( mergedDocument ! = null ) {
mergedDocument . close ( ) ; // Close the merged document
}
2023-08-17 22:03:36 +01:00
}
2023-04-28 23:18:10 +01:00
}
2023-08-17 22:03:36 +01:00
}