cucumber for days (#5766)

This commit is contained in:
Anthony Stirling
2026-02-21 23:17:28 +00:00
committed by GitHub
parent 7631b222bd
commit 30c258ce0b
40 changed files with 4079 additions and 49 deletions
@@ -158,13 +158,23 @@ public class CustomPDFDocumentFactory {
// Since we don't know the size upfront, buffer to a temp file
Path tempFile = createTempFile("pdf-stream-");
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
PDDocument doc = loadAdaptively(tempFile.toFile(), Files.size(tempFile));
if (!readOnly) {
postProcessDocument(doc);
boolean success = false;
try {
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
PDDocument doc = loadAdaptively(tempFile.toFile(), Files.size(tempFile));
if (!readOnly) {
postProcessDocument(doc);
}
success = true;
return doc;
} finally {
// On success: small files are deleted inside loadAdaptively; large files are deleted
// by DeletingRandomAccessFile when the returned PDDocument is closed.
// On failure: clean up the temp file ourselves since no one else will.
if (!success) {
Files.deleteIfExists(tempFile);
}
}
return doc;
}
/** Load with password from InputStream */
@@ -181,14 +191,21 @@ public class CustomPDFDocumentFactory {
// Since we don't know the size upfront, buffer to a temp file
Path tempFile = createTempFile("pdf-stream-");
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
PDDocument doc =
loadAdaptivelyWithPassword(tempFile.toFile(), Files.size(tempFile), password);
if (!readOnly) {
postProcessDocument(doc);
boolean success = false;
try {
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
PDDocument doc =
loadAdaptivelyWithPassword(tempFile.toFile(), Files.size(tempFile), password);
if (!readOnly) {
postProcessDocument(doc);
}
success = true;
return doc;
} finally {
if (!success) {
Files.deleteIfExists(tempFile);
}
}
return doc;
}
/** Load from a file path string */
@@ -358,9 +375,24 @@ public class CustomPDFDocumentFactory {
if (size >= SMALL_FILE_THRESHOLD) {
log.debug("Writing large byte array to temp file for password-protected PDF");
Path tempFile = createTempFile("pdf-bytes-");
Files.write(tempFile, bytes);
return Loader.loadPDF(tempFile.toFile(), password, null, null, cache);
boolean success = false;
try {
Files.write(tempFile, bytes);
// Use DeletingRandomAccessFile so the temp file is removed when the document closes
PDDocument doc =
Loader.loadPDF(
new DeletingRandomAccessFile(tempFile.toFile()),
password,
null,
null,
cache);
success = true;
return doc;
} finally {
if (!success) {
Files.deleteIfExists(tempFile);
}
}
}
return Loader.loadPDF(bytes, password, null, null, cache);
}
@@ -426,9 +458,12 @@ public class CustomPDFDocumentFactory {
}
} else {
Path tempFile = createTempFile("pdf-save-");
document.save(tempFile.toFile());
return Files.readAllBytes(tempFile);
try {
document.save(tempFile.toFile());
return Files.readAllBytes(tempFile);
} finally {
Files.deleteIfExists(tempFile);
}
}
}
@@ -254,10 +254,11 @@ public class JobExecutorService {
return ResponseEntity.internalServerError()
.body(Map.of("error", "Job timed out after " + timeoutToUse + " ms"));
} catch (RuntimeException e) {
// Check if this is a wrapped typed exception that should be handled by
// GlobalExceptionHandler
// Check if this is a typed exception that should be handled by
// GlobalExceptionHandler (either directly or wrapped)
Throwable cause = e.getCause();
if (cause instanceof stirling.software.common.util.ExceptionUtils.BaseAppException
if (e instanceof IllegalArgumentException
|| cause instanceof stirling.software.common.util.ExceptionUtils.BaseAppException
|| cause
instanceof
stirling.software.common.util.ExceptionUtils
@@ -8,10 +8,12 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
@@ -35,9 +37,9 @@ public class AnalysisController {
@Operation(
summary = "Get PDF page count",
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
public Map<String, Integer> getPageCount(@ModelAttribute PDFFile file) throws IOException {
public ResponseEntity<?> getPageCount(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
return Map.of("pageCount", document.getNumberOfPages());
return ResponseEntity.ok(Map.of("pageCount", document.getNumberOfPages()));
}
}
@@ -46,13 +48,13 @@ public class AnalysisController {
@Operation(
summary = "Get basic PDF information",
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getBasicInfo(@ModelAttribute PDFFile file) throws IOException {
public ResponseEntity<?> getBasicInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> info = new HashMap<>();
info.put("pageCount", document.getNumberOfPages());
info.put("pdfVersion", document.getVersion());
info.put("fileSize", file.getFileInput().getSize());
return info;
return ResponseEntity.ok(info);
}
}
@@ -63,7 +65,7 @@ public class AnalysisController {
@Operation(
summary = "Get PDF document properties",
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file)
public ResponseEntity<?> getDocumentProperties(@ModelAttribute PDFFile file)
throws IOException {
// Load the document in read-only mode to prevent modifications and ensure the integrity of
// the original file.
@@ -76,9 +78,15 @@ public class AnalysisController {
properties.put("keywords", info.getKeywords());
properties.put("creator", info.getCreator());
properties.put("producer", info.getProducer());
properties.put("creationDate", info.getCreationDate().toString());
properties.put("modificationDate", info.getModificationDate().toString());
return properties;
properties.put(
"creationDate",
info.getCreationDate() != null ? info.getCreationDate().toString() : null);
properties.put(
"modificationDate",
info.getModificationDate() != null
? info.getModificationDate().toString()
: null);
return ResponseEntity.ok(properties);
}
}
@@ -87,8 +95,7 @@ public class AnalysisController {
@Operation(
summary = "Get page dimensions for all pages",
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
public List<Map<String, Float>> getPageDimensions(@ModelAttribute PDFFile file)
throws IOException {
public ResponseEntity<?> getPageDimensions(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
List<Map<String, Float>> dimensions = new ArrayList<>();
PDPageTree pages = document.getPages();
@@ -99,7 +106,7 @@ public class AnalysisController {
pageDim.put("height", page.getBBox().getHeight());
dimensions.add(pageDim);
}
return dimensions;
return ResponseEntity.ok(dimensions);
}
}
@@ -109,7 +116,7 @@ public class AnalysisController {
summary = "Get form field information",
description =
"Returns count and details of form fields. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getFormFields(@ModelAttribute PDFFile file) throws IOException {
public ResponseEntity<?> getFormFields(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> formInfo = new HashMap<>();
PDAcroForm form = document.getDocumentCatalog().getAcroForm();
@@ -123,7 +130,7 @@ public class AnalysisController {
formInfo.put("hasXFA", false);
formInfo.put("isSignaturesExist", false);
}
return formInfo;
return ResponseEntity.ok(formInfo);
}
}
@@ -132,7 +139,7 @@ public class AnalysisController {
@Operation(
summary = "Get annotation information",
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getAnnotationInfo(@ModelAttribute PDFFile file) throws IOException {
public ResponseEntity<?> getAnnotationInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> annotInfo = new HashMap<>();
int totalAnnotations = 0;
@@ -148,7 +155,7 @@ public class AnalysisController {
annotInfo.put("totalCount", totalAnnotations);
annotInfo.put("typeBreakdown", annotationTypes);
return annotInfo;
return ResponseEntity.ok(annotInfo);
}
}
@@ -158,20 +165,23 @@ public class AnalysisController {
summary = "Get font information",
description =
"Returns list of fonts used in the document. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getFontInfo(@ModelAttribute PDFFile file) throws IOException {
public ResponseEntity<?> getFontInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> fontInfo = new HashMap<>();
Set<String> fontNames = new HashSet<>();
for (PDPage page : document.getPages()) {
for (COSName font : page.getResources().getFontNames()) {
fontNames.add(font.getName());
PDResources resources = page.getResources();
if (resources != null) {
for (COSName font : resources.getFontNames()) {
fontNames.add(font.getName());
}
}
}
fontInfo.put("fontCount", fontNames.size());
fontInfo.put("fonts", fontNames);
return fontInfo;
return ResponseEntity.ok(fontInfo);
}
}
@@ -181,7 +191,7 @@ public class AnalysisController {
summary = "Get security information",
description =
"Returns encryption and permission details. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getSecurityInfo(@ModelAttribute PDFFile file) throws IOException {
public ResponseEntity<?> getSecurityInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> securityInfo = new HashMap<>();
PDEncryption encryption = document.getEncryption();
@@ -208,7 +218,7 @@ public class AnalysisController {
securityInfo.put("isEncrypted", false);
}
return securityInfo;
return ResponseEntity.ok(securityInfo);
}
}
}
@@ -399,7 +399,12 @@ public class MergeController {
String mergedFileName =
GeneralUtils.generateFilename(firstFilename, "_merged_unsigned.pdf");
byte[] pdfBytes = Files.readAllBytes(outputTempFile.getPath());
byte[] pdfBytes;
try {
pdfBytes = Files.readAllBytes(outputTempFile.getPath());
} finally {
outputTempFile.close();
}
return WebResponseUtils.bytesToWebResponse(pdfBytes, mergedFileName);
}
}