added tests

This commit is contained in:
Dario Ghunney Ware 2025-06-18 11:35:15 +01:00
parent caef177ec3
commit 95a128ca31
9 changed files with 133 additions and 16 deletions

View File

@ -27,6 +27,7 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PageMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
@ -1271,7 +1272,7 @@ public class EmlToPdf {
efTree.setNames(efMap); efTree.setNames(efMap);
// Set catalog viewer preferences to automatically show attachments pane // Set catalog viewer preferences to automatically show attachments pane
setCatalogViewerPreferences(document); setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
} }
// Add attachment annotations to the first page for each embedded file // Add attachment annotations to the first page for each embedded file

View File

@ -10,7 +10,7 @@ import org.apache.pdfbox.pdmodel.PageMode;
@Slf4j @Slf4j
public class PDFAttachmentUtils { public class PDFAttachmentUtils {
public static void setCatalogViewerPreferences(PDDocument document) { public static void setCatalogViewerPreferences(PDDocument document, PageMode pageMode) {
try { try {
PDDocumentCatalog catalog = document.getDocumentCatalog(); PDDocumentCatalog catalog = document.getDocumentCatalog();
if (catalog != null) { if (catalog != null) {
@ -19,7 +19,8 @@ public class PDFAttachmentUtils {
// Set PageMode to UseAttachments - this is the standard PDF specification approach // Set PageMode to UseAttachments - this is the standard PDF specification approach
// PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, UseAttachments // PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, UseAttachments
catalogDict.setName(COSName.PAGE_MODE, PageMode.USE_ATTACHMENTS.stringValue()); catalog.setPageMode(pageMode);
catalogDict.setName(COSName.PAGE_MODE, pageMode.stringValue());
// Also set viewer preferences for better attachment viewing experience // Also set viewer preferences for better attachment viewing experience
COSDictionary viewerPrefs = (COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES); COSDictionary viewerPrefs = (COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
@ -29,7 +30,7 @@ public class PDFAttachmentUtils {
} }
// Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support it // Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support it
viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), PageMode.USE_ATTACHMENTS.stringValue()); viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), pageMode.stringValue());
// Additional viewer preferences that may help with attachment display // Additional viewer preferences that may help with attachment display
viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true); viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true);

View File

@ -61,7 +61,6 @@ public class WebResponseUtils {
// Open Byte Array and save document to it // Open Byte Array and save document to it
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos); document.save(baos);
// Close the document
document.close(); document.close();
return baosToWebResponse(baos, docName); return baosToWebResponse(baos, docName);

View File

@ -62,9 +62,6 @@ public class AttachmentsController {
catalog.setNames(documentNames); catalog.setNames(documentNames);
pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments); pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
// Set PageMode to UseAttachments to show the attachments panel
catalog.setPageMode(PageMode.USE_ATTACHMENTS);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) Filenames.toSimpleFileName(pdfFile.getOriginalFilename())

View File

@ -195,7 +195,7 @@ public class OtherWebController {
@GetMapping("/add-attachments") @GetMapping("/add-attachments")
@Hidden @Hidden
public String attachmentsForm(Model model) { public String attachmentsForm(Model model) {
model.addAttribute("currentPage", "attachments"); model.addAttribute("currentPage", "add-attachments");
return "misc/add-attachments"; return "misc/add-attachments";
} }
} }

View File

@ -9,6 +9,7 @@ import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PageMode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
@ -91,9 +92,8 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
embeddedFilesTree.setNames(existingNames); embeddedFilesTree.setNames(existingNames);
// Ensure document has proper access permissions for embedded files
grantAccessPermissions(document); grantAccessPermissions(document);
PDFAttachmentUtils.setCatalogViewerPreferences(document); PDFAttachmentUtils.setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
} }
private void grantAccessPermissions(PDDocument document) { private void grantAccessPermissions(PDDocument document) {

View File

@ -525,7 +525,7 @@ home.addImage.title=Add image
home.addImage.desc=Adds a image onto a set location on the PDF home.addImage.desc=Adds a image onto a set location on the PDF
addImage.tags=img,jpg,picture,photo addImage.tags=img,jpg,picture,photo
home.attachments.title=Attachments home.attachments.title=Add Attachments
home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF
attachments.tags=embed,attach,file,attachment,attachments attachments.tags=embed,attach,file,attachment,attachments
@ -1210,8 +1210,8 @@ addImage.upload=Add image
addImage.submit=Add image addImage.submit=Add image
#attachments #attachments
attachments.title=Attachments attachments.title=Add Attachments
attachments.header=Add attachments to PDF attachments.header=Add attachments
attachments.removeHeader=Remove attachments from PDF attachments.removeHeader=Remove attachments from PDF
attachments.selectFiles=Select files to attach attachments.selectFiles=Select files to attach
attachments.description=Allows you to add attachments to the PDF attachments.description=Allows you to add attachments to the PDF

View File

@ -525,7 +525,7 @@ home.addImage.title=Add image
home.addImage.desc=Adds a image onto a set location on the PDF home.addImage.desc=Adds a image onto a set location on the PDF
addImage.tags=img,jpg,picture,photo addImage.tags=img,jpg,picture,photo
home.attachments.title=Attachments home.attachments.title=Add Attachments
home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF
attachments.tags=embed,attach,file,attachment,attachments attachments.tags=embed,attach,file,attachment,attachments
@ -1211,7 +1211,7 @@ addImage.submit=Add image
#attachments #attachments
attachments.title=Attachments attachments.title=Attachments
attachments.header=Add attachments to PDF attachments.header=Add attachments
attachments.removeHeader=Remove attachments from PDF attachments.removeHeader=Remove attachments from PDF
attachments.selectFiles=Select files to attach attachments.selectFiles=Select files to attach
attachments.description=Allows you to add attachments to the PDF attachments.description=Allows you to add attachments to the PDF

View File

@ -0,0 +1,119 @@
package stirling.software.SPDF.service;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.multipart.MultipartFile;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class PDFAttachmentServiceTest {
private PDFAttachmentService pdfAttachmentService;
@BeforeEach
void setUp() {
pdfAttachmentService = new PDFAttachmentService();
}
@Test
void addAttachmentToPDF() throws IOException {
try (var document = new PDDocument()) {
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var existingNames = new HashMap<String, PDComplexFileSpecification>();
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
when(attachments.get(0).getInputStream()).thenReturn(
new ByteArrayInputStream("Test content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(12L);
when(attachments.get(0).getContentType()).thenReturn("text/plain");
pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
verify(embeddedFilesTree).setNames(anyMap());
}
}
@Test
void addAttachmentToPDF_WithNullExistingNames() throws IOException {
try (var document = new PDDocument()) {
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
when(embeddedFilesTree.getNames()).thenReturn(null);
when(attachments.get(0).getOriginalFilename()).thenReturn("document.pdf");
when(attachments.get(0).getInputStream()).thenReturn(
new ByteArrayInputStream("PDF content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(15L);
when(attachments.get(0).getContentType()).thenReturn("application/pdf");
pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
verify(embeddedFilesTree).setNames(anyMap());
}
}
@Test
void addAttachmentToPDF_WithBlankContentType() throws IOException {
try (var document = new PDDocument()) {
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var existingNames = new HashMap<String, PDComplexFileSpecification>();
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
when(attachments.get(0).getOriginalFilename()).thenReturn("image.jpg");
when(attachments.get(0).getInputStream()).thenReturn(
new ByteArrayInputStream("Image content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(25L);
when(attachments.get(0).getContentType()).thenReturn("");
pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
verify(embeddedFilesTree).setNames(anyMap());
}
}
@Test
void addAttachmentToPDF_GetNamesThrowsIOException() throws IOException {
var document = mock(PDDocument.class);
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var ioException = new IOException("Failed to retrieve embedded files");
when(embeddedFilesTree.getNames()).thenThrow(ioException);
assertThrows(IOException.class, () -> pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments));
verify(embeddedFilesTree).getNames();
}
@Test
void addAttachmentToPDF_AttachmentInputStreamThrowsIOException() throws IOException {
try (var document = new PDDocument()) {
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var existingNames = new HashMap<String, PDComplexFileSpecification>();
var ioException = new IOException("Failed to read attachment stream");
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
when(attachments.get(0).getOriginalFilename()).thenReturn("corrupted.file");
when(attachments.get(0).getInputStream()).thenThrow(ioException);
when(attachments.get(0).getSize()).thenReturn(10L);
pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
verify(embeddedFilesTree).setNames(anyMap());
}
}
}