auto formatting

This commit is contained in:
Anthony Stirling 2025-07-02 10:27:09 +01:00
parent e764158178
commit 0b045bc23a
22 changed files with 360 additions and 290 deletions

View File

@ -25,7 +25,6 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.util.ApplicationContextProvider;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.PdfErrorUtils;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.TempFileRegistry;
@ -373,7 +372,7 @@ public class CustomPDFDocumentFactory {
Files.write(tempFile, bytes);
return loadFromFile(tempFile.toFile(), size, cache);
}
try {
return Loader.loadPDF(bytes, "", null, null, cache);
} catch (IOException e) {

View File

@ -44,7 +44,6 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.converters.EmlToPdfRequest;
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
@Slf4j
@UtilityClass

View File

@ -5,25 +5,25 @@ import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
/**
* Utility class for handling exceptions with internationalized error messages.
* Provides consistent error handling and user-friendly messages across the application.
* Utility class for handling exceptions with internationalized error messages. Provides consistent
* error handling and user-friendly messages across the application.
*/
@Slf4j
public class ExceptionUtils {
/**
* Create an IOException with internationalized message for PDF corruption.
*
*
* @param cause the original exception
* @return IOException with user-friendly message
*/
public static IOException createPdfCorruptedException(Exception cause) {
return createPdfCorruptedException(null, cause);
}
/**
* Create an IOException with internationalized message for PDF corruption with context.
*
*
* @param context additional context (e.g., "during merge", "during image extraction")
* @param cause the original exception
* @return IOException with user-friendly message
@ -31,295 +31,319 @@ public class ExceptionUtils {
public static IOException createPdfCorruptedException(String context, Exception cause) {
String message;
if (context != null && !context.isEmpty()) {
message = I18nUtils.getMessage(
"error.pdfCorruptedDuring",
"Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
context
);
message =
I18nUtils.getMessage(
"error.pdfCorruptedDuring",
"Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
context);
} else {
message = I18nUtils.getMessage(
"error.pdfCorrupted",
"PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation."
);
message =
I18nUtils.getMessage(
"error.pdfCorrupted",
"PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.");
}
return new IOException(message, cause);
}
/**
* Create an IOException with internationalized message for multiple corrupted PDFs.
*
*
* @param cause the original exception
* @return IOException with user-friendly message
*/
public static IOException createMultiplePdfCorruptedException(Exception cause) {
String message = I18nUtils.getMessage(
"error.pdfCorruptedMultiple",
"One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them."
);
String message =
I18nUtils.getMessage(
"error.pdfCorruptedMultiple",
"One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them.");
return new IOException(message, cause);
}
/**
* Create an IOException with internationalized message for PDF encryption issues.
*
*
* @param cause the original exception
* @return IOException with user-friendly message
*/
public static IOException createPdfEncryptionException(Exception cause) {
String message = I18nUtils.getMessage(
"error.pdfEncryption",
"The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy.",
cause.getMessage()
);
String message =
I18nUtils.getMessage(
"error.pdfEncryption",
"The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy.",
cause.getMessage());
return new IOException(message, cause);
}
/**
* Create an IOException with internationalized message for PDF password issues.
*
*
* @param cause the original exception
* @return IOException with user-friendly message
*/
public static IOException createPdfPasswordException(Exception cause) {
String message = I18nUtils.getMessage(
"error.pdfPassword",
"The PDF Document is passworded and either the password was not provided or was incorrect"
);
String message =
I18nUtils.getMessage(
"error.pdfPassword",
"The PDF Document is passworded and either the password was not provided or was incorrect");
return new IOException(message, cause);
}
/**
* Create an IOException with internationalized message for file processing errors.
*
*
* @param operation the operation being performed (e.g., "merge", "split", "convert")
* @param cause the original exception
* @return IOException with user-friendly message
*/
public static IOException createFileProcessingException(String operation, Exception cause) {
String message = I18nUtils.getMessage(
"error.fileProcessing",
"An error occurred while processing the file during {0} operation: {1}",
operation,
cause.getMessage()
);
String message =
I18nUtils.getMessage(
"error.fileProcessing",
"An error occurred while processing the file during {0} operation: {1}",
operation,
cause.getMessage());
return new IOException(message, cause);
}
/**
* Create a generic IOException with internationalized message.
*
*
* @param messageKey the i18n message key
* @param defaultMessage the default message if i18n is not available
* @param cause the original exception
* @param args optional arguments for the message
* @return IOException with user-friendly message
*/
public static IOException createIOException(String messageKey, String defaultMessage, Exception cause, Object... args) {
public static IOException createIOException(
String messageKey, String defaultMessage, Exception cause, Object... args) {
String message = I18nUtils.getMessage(messageKey, defaultMessage, args);
return new IOException(message, cause);
}
/**
* Create a generic RuntimeException with internationalized message.
*
*
* @param messageKey the i18n message key
* @param defaultMessage the default message if i18n is not available
* @param cause the original exception
* @param args optional arguments for the message
* @return RuntimeException with user-friendly message
*/
public static RuntimeException createRuntimeException(String messageKey, String defaultMessage, Exception cause, Object... args) {
public static RuntimeException createRuntimeException(
String messageKey, String defaultMessage, Exception cause, Object... args) {
String message = I18nUtils.getMessage(messageKey, defaultMessage, args);
return new RuntimeException(message, cause);
}
/**
* Create an IllegalArgumentException with internationalized message.
*
*
* @param messageKey the i18n message key
* @param defaultMessage the default message if i18n is not available
* @param args optional arguments for the message
* @return IllegalArgumentException with user-friendly message
*/
public static IllegalArgumentException createIllegalArgumentException(String messageKey, String defaultMessage, Object... args) {
public static IllegalArgumentException createIllegalArgumentException(
String messageKey, String defaultMessage, Object... args) {
String message = I18nUtils.getMessage(messageKey, defaultMessage, args);
System.out.println("######## Test " + message);
return new IllegalArgumentException(message);
}
/**
* Create file validation exceptions.
*/
/** Create file validation exceptions. */
public static IllegalArgumentException createHtmlFileRequiredException() {
return createIllegalArgumentException("error.fileFormatRequired", "File must be in {0} format", "HTML or ZIP");
return createIllegalArgumentException(
"error.fileFormatRequired", "File must be in {0} format", "HTML or ZIP");
}
public static IllegalArgumentException createMarkdownFileRequiredException() {
return createIllegalArgumentException("error.fileFormatRequired", "File must be in {0} format", "Markdown");
return createIllegalArgumentException(
"error.fileFormatRequired", "File must be in {0} format", "Markdown");
}
public static IllegalArgumentException createMarkdownFormatException() {
return createIllegalArgumentException("error.fileFormatRequired", "File must be in {0} format", ".md");
return createIllegalArgumentException(
"error.fileFormatRequired", "File must be in {0} format", ".md");
}
public static IllegalArgumentException createHtmlZipFormatException() {
return createIllegalArgumentException("error.fileFormatRequired", "File must be in {0} format", ".html or .zip");
return createIllegalArgumentException(
"error.fileFormatRequired", "File must be in {0} format", ".html or .zip");
}
public static IllegalArgumentException createPdfFileRequiredException() {
return createIllegalArgumentException("error.fileFormatRequired", "File must be in {0} format", "PDF");
return createIllegalArgumentException(
"error.fileFormatRequired", "File must be in {0} format", "PDF");
}
public static IllegalArgumentException createInvalidPageSizeException(String size) {
return createIllegalArgumentException("error.invalidFormat", "Invalid {0} format: {1}", "page size", size);
return createIllegalArgumentException(
"error.invalidFormat", "Invalid {0} format: {1}", "page size", size);
}
/**
* Create OCR-related exceptions.
*/
/** Create OCR-related exceptions. */
public static IOException createOcrLanguageRequiredException() {
return createIOException("error.optionsNotSpecified", "{0} options are not specified", null, "OCR language");
return createIOException(
"error.optionsNotSpecified", "{0} options are not specified", null, "OCR language");
}
public static IOException createOcrInvalidLanguagesException() {
return createIOException("error.invalidFormat", "Invalid {0} format: {1}", null, "OCR languages", "none of the selected languages are valid");
return createIOException(
"error.invalidFormat",
"Invalid {0} format: {1}",
null,
"OCR languages",
"none of the selected languages are valid");
}
public static IOException createOcrToolsUnavailableException() {
return createIOException("error.toolNotInstalled", "{0} is not installed", null, "OCR tools");
return createIOException(
"error.toolNotInstalled", "{0} is not installed", null, "OCR tools");
}
/**
* Create URL/website conversion exceptions.
*/
/** Create URL/website conversion exceptions. */
public static IllegalArgumentException createInvalidUrlFormatException() {
return createIllegalArgumentException("error.invalidFormat", "Invalid {0} format: {1}", "URL", "provided format is invalid");
return createIllegalArgumentException(
"error.invalidFormat",
"Invalid {0} format: {1}",
"URL",
"provided format is invalid");
}
public static IllegalArgumentException createUrlNotReachableException() {
return createIllegalArgumentException("error.urlNotReachable", "URL is not reachable, please provide a valid URL");
return createIllegalArgumentException(
"error.urlNotReachable", "URL is not reachable, please provide a valid URL");
}
public static IllegalArgumentException createEndpointDisabledException() {
return createIllegalArgumentException("error.endpointDisabled", "This endpoint has been disabled by the admin");
return createIllegalArgumentException(
"error.endpointDisabled", "This endpoint has been disabled by the admin");
}
/**
* Create system requirement exceptions.
*/
/** Create system requirement exceptions. */
public static IOException createPythonNotInstalledException() {
return createIOException("error.toolNotInstalled", "{0} is not installed", null, "Python");
}
public static IOException createPythonRequiredForWebpException() {
return createIOException("error.toolRequired", "{0} is required for {1}", null, "Python", "WebP conversion");
return createIOException(
"error.toolRequired", "{0} is required for {1}", null, "Python", "WebP conversion");
}
/**
* Create file operation exceptions.
*/
/** Create file operation exceptions. */
public static IOException createFileNotFoundException(String fileId) {
return createIOException("error.fileNotFound", "File not found with ID: {0}", null, fileId);
}
public static RuntimeException createPdfaConversionFailedException() {
return createRuntimeException("error.conversionFailed", "{0} conversion failed", null, "PDF/A");
return createRuntimeException(
"error.conversionFailed", "{0} conversion failed", null, "PDF/A");
}
public static IllegalArgumentException createInvalidComparatorException() {
return createIllegalArgumentException("error.invalidFormat", "Invalid {0} format: {1}", "comparator", "only 'greater', 'equal', and 'less' are supported");
return createIllegalArgumentException(
"error.invalidFormat",
"Invalid {0} format: {1}",
"comparator",
"only 'greater', 'equal', and 'less' are supported");
}
/**
* Create compression-related exceptions.
*/
/** Create compression-related exceptions. */
public static RuntimeException createMd5AlgorithmException(Exception cause) {
return createRuntimeException("error.algorithmNotAvailable", "{0} algorithm not available", cause, "MD5");
return createRuntimeException(
"error.algorithmNotAvailable", "{0} algorithm not available", cause, "MD5");
}
public static IllegalArgumentException createCompressionOptionsException() {
return createIllegalArgumentException("error.optionsNotSpecified", "{0} options are not specified", "compression (expected output size and optimize level)");
return createIllegalArgumentException(
"error.optionsNotSpecified",
"{0} options are not specified",
"compression (expected output size and optimize level)");
}
public static IOException createGhostscriptCompressionException() {
return createIOException("error.commandFailed", "{0} command failed", null, "Ghostscript compression");
return createIOException(
"error.commandFailed", "{0} command failed", null, "Ghostscript compression");
}
public static IOException createGhostscriptCompressionException(Exception cause) {
return createIOException("error.commandFailed", "{0} command failed", cause, "Ghostscript compression");
return createIOException(
"error.commandFailed", "{0} command failed", cause, "Ghostscript compression");
}
public static IOException createQpdfCompressionException(Exception cause) {
return createIOException("error.commandFailed", "{0} command failed", cause, "QPDF");
}
/**
* Check if an exception indicates a corrupted PDF and wrap it with appropriate message.
*
*
* @param e the exception to check
* @return the original exception if not PDF corruption, or a new IOException with user-friendly message
* @return the original exception if not PDF corruption, or a new IOException with user-friendly
* message
*/
public static IOException handlePdfException(IOException e) {
return handlePdfException(e, null);
}
/**
* Check if an exception indicates a corrupted PDF and wrap it with appropriate message.
*
*
* @param e the exception to check
* @param context additional context for the error
* @return the original exception if not PDF corruption, or a new IOException with user-friendly message
* @return the original exception if not PDF corruption, or a new IOException with user-friendly
* message
*/
public static IOException handlePdfException(IOException e, String context) {
if (PdfErrorUtils.isCorruptedPdfError(e)) {
return createPdfCorruptedException(context, e);
}
if (isEncryptionError(e)) {
return createPdfEncryptionException(e);
}
if (isPasswordError(e)) {
return createPdfPasswordException(e);
}
return e; // Return original exception if no specific handling needed
}
/**
* Check if an exception indicates a PDF encryption/decryption error.
*
*
* @param e the exception to check
* @return true if it's an encryption error, false otherwise
*/
public static boolean isEncryptionError(IOException e) {
String message = e.getMessage();
if (message == null) return false;
return message.contains("BadPaddingException") ||
message.contains("Given final block not properly padded") ||
message.contains("AES initialization vector not fully read") ||
message.contains("Failed to decrypt");
return message.contains("BadPaddingException")
|| message.contains("Given final block not properly padded")
|| message.contains("AES initialization vector not fully read")
|| message.contains("Failed to decrypt");
}
/**
* Check if an exception indicates a PDF password error.
*
*
* @param e the exception to check
* @return true if it's a password error, false otherwise
*/
public static boolean isPasswordError(IOException e) {
String message = e.getMessage();
if (message == null) return false;
return message.contains("password is incorrect") ||
message.contains("Password is not provided") ||
message.contains("PDF contains an encryption dictionary");
return message.contains("password is incorrect")
|| message.contains("Password is not provided")
|| message.contains("PDF contains an encryption dictionary");
}
/**
* Log an exception with appropriate level based on its type.
*
*
* @param operation the operation being performed
* @param e the exception that occurred
*/
@ -332,4 +356,4 @@ public class ExceptionUtils {
log.error("Unexpected error during {}", operation, e);
}
}
}
}

View File

@ -8,17 +8,17 @@ import org.springframework.context.i18n.LocaleContextHolder;
import lombok.extern.slf4j.Slf4j;
/**
* Utility class for internationalized (i18n) message handling.
* Provides centralized access to Spring MessageSource for consistent error messaging.
* Utility class for internationalized (i18n) message handling. Provides centralized access to
* Spring MessageSource for consistent error messaging.
*/
@Slf4j
public class I18nUtils {
private static MessageSource messageSource;
/**
* Get the MessageSource bean from the application context.
*
*
* @return MessageSource instance, or null if not available
*/
public static MessageSource getMessageSource() {
@ -32,10 +32,10 @@ public class I18nUtils {
}
return messageSource;
}
/**
* Get a localized message for the given key with parameters.
*
*
* @param key the message key
* @param args optional arguments for the message
* @return the localized message, or the key itself if message source is not available
@ -43,10 +43,10 @@ public class I18nUtils {
public static String getMessage(String key, Object... args) {
return getMessage(key, LocaleContextHolder.getLocale(), args);
}
/**
* Get a localized message for the given key with specific locale and parameters.
*
*
* @param key the message key
* @param locale the locale to use
* @param args optional arguments for the message
@ -61,18 +61,24 @@ public class I18nUtils {
log.debug("Failed to get message for key '{}': {}", key, e.getMessage());
}
}
// Fallback: return the key with arguments if available
if (args != null && args.length > 0) {
return key + " [" + String.join(", ", java.util.Arrays.stream(args)
.map(Object::toString).toArray(String[]::new)) + "]";
return key
+ " ["
+ String.join(
", ",
java.util.Arrays.stream(args)
.map(Object::toString)
.toArray(String[]::new))
+ "]";
}
return key;
}
/**
* Get a localized message with a fallback default message.
*
*
* @param key the message key
* @param defaultMessage the default message to use if key is not found
* @param args optional arguments for the message
@ -87,7 +93,7 @@ public class I18nUtils {
log.debug("Failed to get message for key '{}': {}", key, e.getMessage());
}
}
// Apply arguments to default message if it contains placeholders
if (defaultMessage != null && args != null && args.length > 0) {
try {
@ -96,16 +102,16 @@ public class I18nUtils {
log.debug("Failed to format default message: {}", e.getMessage());
}
}
return defaultMessage != null ? defaultMessage : key;
}
/**
* Check if MessageSource is available.
*
*
* @return true if MessageSource is available, false otherwise
*/
public static boolean isMessageSourceAvailable() {
return getMessageSource() != null;
}
}
}

View File

@ -3,105 +3,108 @@ package stirling.software.common.util;
import java.io.IOException;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
* Utility class for detecting and handling PDF-related errors.
*/
/** Utility class for detecting and handling PDF-related errors. */
public class PdfErrorUtils {
/**
* Checks if an IOException indicates a corrupted PDF file.
*
*
* @param e the IOException to check
* @return true if the error indicates PDF corruption, false otherwise
*/
public static boolean isCorruptedPdfError(IOException e) {
String message = e.getMessage();
if (message == null) return false;
// Check for common corruption indicators
return message.contains("Missing root object specification") ||
message.contains("Header doesn't contain versioninfo") ||
message.contains("Expected trailer") ||
message.contains("Invalid PDF") ||
message.contains("Corrupted") ||
message.contains("damaged") ||
message.contains("Unknown dir object") ||
message.contains("Can't dereference COSObject") ||
message.contains("AES initialization vector not fully read") ||
message.contains("BadPaddingException") ||
message.contains("Given final block not properly padded");
return message.contains("Missing root object specification")
|| message.contains("Header doesn't contain versioninfo")
|| message.contains("Expected trailer")
|| message.contains("Invalid PDF")
|| message.contains("Corrupted")
|| message.contains("damaged")
|| message.contains("Unknown dir object")
|| message.contains("Can't dereference COSObject")
|| message.contains("AES initialization vector not fully read")
|| message.contains("BadPaddingException")
|| message.contains("Given final block not properly padded");
}
/**
* Creates a user-friendly error message for corrupted PDF files using i18n.
*
*
* @param messageSource the Spring MessageSource for i18n
* @return a user-friendly error message
* @deprecated Use ExceptionUtils.createPdfCorruptedException() instead
*/
@Deprecated
public static String getCorruptedPdfMessage(MessageSource messageSource) {
return I18nUtils.getMessage("error.pdfCorrupted",
"PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.");
return I18nUtils.getMessage(
"error.pdfCorrupted",
"PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.");
}
/**
* Creates a user-friendly error message for corrupted PDF files with context using i18n.
*
*
* @param messageSource the Spring MessageSource for i18n
* @param context additional context about where the error occurred (e.g., "during merge", "during processing")
* @param context additional context about where the error occurred (e.g., "during merge",
* "during processing")
* @return a user-friendly error message
* @deprecated Use ExceptionUtils.createPdfCorruptedException(context, cause) instead
*/
@Deprecated
public static String getCorruptedPdfMessage(MessageSource messageSource, String context) {
if (context != null && !context.isEmpty()) {
return I18nUtils.getMessage("error.pdfCorruptedDuring",
"Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
context);
return I18nUtils.getMessage(
"error.pdfCorruptedDuring",
"Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
context);
}
return getCorruptedPdfMessage(messageSource);
}
/**
* Creates a user-friendly error message for multiple corrupted PDF files (e.g., during merge) using i18n.
*
* Creates a user-friendly error message for multiple corrupted PDF files (e.g., during merge)
* using i18n.
*
* @param messageSource the Spring MessageSource for i18n
* @return a user-friendly error message for multiple file operations
* @deprecated Use ExceptionUtils.createMultiplePdfCorruptedException() instead
*/
@Deprecated
public static String getCorruptedPdfMessageForMultipleFiles(MessageSource messageSource) {
return I18nUtils.getMessage("error.pdfCorruptedMultiple",
"One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them.");
return I18nUtils.getMessage(
"error.pdfCorruptedMultiple",
"One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them.");
}
// Fallback methods for backwards compatibility (when MessageSource is not available)
/**
* Creates a user-friendly error message for corrupted PDF files (fallback).
*
*
* @param context additional context about where the error occurred
* @return a user-friendly error message
*/
public static String getCorruptedPdfMessage(String context) {
String baseMessage = "PDF file appears to be corrupted or damaged. " +
"Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.";
String baseMessage =
"PDF file appears to be corrupted or damaged. "
+ "Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.";
if (context != null && !context.isEmpty()) {
return "Error " + context + ": " + baseMessage;
}
return baseMessage;
}
/**
* Creates a user-friendly error message for multiple corrupted PDF files (fallback).
*
*
* @return a user-friendly error message for multiple file operations
*/
public static String getCorruptedPdfMessageForMultipleFiles() {
return "One or more PDF files appear to be corrupted or damaged. " +
"Please try using the 'Repair PDF' feature on each file first before attempting to merge them.";
return "One or more PDF files appear to be corrupted or damaged. "
+ "Please try using the 'Repair PDF' feature on each file first before attempting to merge them.";
}
}
}

View File

@ -36,7 +36,6 @@ import io.github.pixee.security.Filenames;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
@Slf4j
public class PdfUtils {
@ -136,16 +135,18 @@ public class PdfUtils {
int DPI,
String filename)
throws IOException, Exception {
// Validate and limit DPI to prevent excessive memory usage
final int MAX_SAFE_DPI = 300; // Maximum safe DPI to prevent memory issues
if (DPI > MAX_SAFE_DPI) {
throw new IllegalArgumentException(String.format(
"DPI value %d exceeds maximum safe limit of %d. " +
"High DPI values can cause memory issues and crashes. " +
"Please use a lower DPI value.", DPI, MAX_SAFE_DPI));
throw new IllegalArgumentException(
String.format(
"DPI value %d exceeds maximum safe limit of %d. "
+ "High DPI values can cause memory issues and crashes. "
+ "Please use a lower DPI value.",
DPI, MAX_SAFE_DPI));
}
try (PDDocument document = pdfDocumentFactory.load(inputStream)) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
@ -173,11 +174,15 @@ public class PdfUtils {
try {
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(String.format(
"PDF page %d is too large to render at %d DPI. " +
"Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI), e);
if (e.getMessage() != null
&& e.getMessage()
.contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(
String.format(
"PDF page %d is too large to render at %d DPI. "
+ "Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI),
e);
}
throw e;
}
@ -215,12 +220,16 @@ public class PdfUtils {
try {
pdfSizeImage = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(String.format(
"PDF page %d is too large to render at %d DPI. " +
"The resulting image would exceed Java's maximum array size. " +
"Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI), e);
if (e.getMessage() != null
&& e.getMessage()
.contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(
String.format(
"PDF page %d is too large to render at %d DPI. "
+ "The resulting image would exceed Java's maximum array size. "
+ "Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI),
e);
}
throw e;
}
@ -254,11 +263,15 @@ public class PdfUtils {
try {
pageImage = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(String.format(
"PDF page %d is too large to render at %d DPI. " +
"Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI), e);
if (e.getMessage() != null
&& e.getMessage()
.contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(
String.format(
"PDF page %d is too large to render at %d DPI. "
+ "Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI),
e);
}
throw e;
}
@ -285,11 +298,14 @@ public class PdfUtils {
try {
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(String.format(
"PDF page %d is too large to render at %d DPI. " +
"Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI), e);
if (e.getMessage() != null
&& e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(
String.format(
"PDF page %d is too large to render at %d DPI. "
+ "Please try a lower DPI value (recommended: 150 or less).",
i + 1, DPI),
e);
}
throw e;
}
@ -334,12 +350,15 @@ public class PdfUtils {
try {
bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(String.format(
"PDF page %d is too large to render at 300 DPI. " +
"The resulting image would exceed Java's maximum array size. " +
"Please use a lower DPI value for PDF-to-image conversion.",
page + 1), e);
if (e.getMessage() != null
&& e.getMessage().contains("Maximum size of image exceeded")) {
throw new IllegalArgumentException(
String.format(
"PDF page %d is too large to render at 300 DPI. "
+ "The resulting image would exceed Java's maximum array size. "
+ "Please use a lower DPI value for PDF-to-image conversion.",
page + 1),
e);
}
throw e;
}

View File

@ -109,8 +109,10 @@ public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
log.info("text not supported by font ");
font = checkSupportedFontForCharacter(unicodeText);
} catch (UnsupportedOperationException ue) {
log.info("font does not support encoding operation: {} for text: '{}'",
font.getClass().getSimpleName(), unicodeText);
log.info(
"font does not support encoding operation: {} for text: '{}'",
font.getClass().getSimpleName(),
unicodeText);
font = checkSupportedFontForCharacter(unicodeText);
} finally {
// if any other font is not supported, then replace default character *

View File

@ -20,7 +20,6 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
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.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@ -194,7 +193,8 @@ public class MergeController {
try {
mergerUtility.mergeDocuments(
pdfDocumentFactory.getStreamCacheFunction(totalSize)); // Merge the documents
pdfDocumentFactory.getStreamCacheFunction(
totalSize)); // Merge the documents
} catch (IOException e) {
ExceptionUtils.logException("PDF merge", e);
if (PdfErrorUtils.isCorruptedPdfError(e)) {

View File

@ -44,7 +44,8 @@ public class RotationController {
// Validate the angle is a multiple of 90
if (angle % 90 != 0) {
throw ExceptionUtils.createIllegalArgumentException("error.angleNotMultipleOf90", "Angle must be a multiple of 90");
throw ExceptionUtils.createIllegalArgumentException(
"error.angleNotMultipleOf90", "Angle must be a multiple of 90");
}
// Load the PDF document

View File

@ -132,7 +132,8 @@ public class SplitPdfByChaptersController {
Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) {
throw ExceptionUtils.createIllegalArgumentException("error.invalidArgument", "Invalid argument: {0}", "bookmark level");
throw ExceptionUtils.createIllegalArgumentException(
"error.invalidArgument", "Invalid argument: {0}", "bookmark level");
}
sourceDocument = pdfDocumentFactory.load(file);
@ -140,7 +141,8 @@ public class SplitPdfByChaptersController {
if (outline == null) {
log.warn("No outline found for {}", file.getOriginalFilename());
throw ExceptionUtils.createIllegalArgumentException("error.pdfBookmarksNotFound", "No PDF bookmarks/outline found in document");
throw ExceptionUtils.createIllegalArgumentException(
"error.pdfBookmarksNotFound", "No PDF bookmarks/outline found in document");
}
List<Bookmark> bookmarks = new ArrayList<>();
try {

View File

@ -98,8 +98,8 @@ public class SplitPdfBySizeController {
} else {
log.error("Invalid split type: {}", type);
throw ExceptionUtils.createIllegalArgumentException(
"error.invalidArgument",
"Invalid argument: {0}",
"error.invalidArgument",
"Invalid argument: {0}",
"split type: " + type);
}

View File

@ -34,12 +34,12 @@ import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CheckProgramInstall;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.ExceptionUtils;
@RestController
@RequestMapping("/api/v1/convert")

View File

@ -67,10 +67,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.ExceptionUtils;
@RestController
@RequestMapping("/api/v1/convert")

View File

@ -53,11 +53,11 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.ExceptionUtils;
@RestController
@RequestMapping("/api/v1/misc")

View File

@ -24,7 +24,6 @@ import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.context.MessageSource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@ -44,7 +43,6 @@ import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ImageProcessingUtils;
import stirling.software.common.util.PdfErrorUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@ -185,34 +183,34 @@ public class ExtractImagesController {
for (COSName name : page.getResources().getXObjectNames()) {
try {
if (page.getResources().isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
if (!allowDuplicates) {
byte[] data = ImageProcessingUtils.getImageData(image.getImage());
byte[] imageHash = md.digest(data);
synchronized (processedImages) {
if (processedImages.stream()
.anyMatch(hash -> Arrays.equals(hash, imageHash))) {
continue; // Skip already processed images
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
if (!allowDuplicates) {
byte[] data = ImageProcessingUtils.getImageData(image.getImage());
byte[] imageHash = md.digest(data);
synchronized (processedImages) {
if (processedImages.stream()
.anyMatch(hash -> Arrays.equals(hash, imageHash))) {
continue; // Skip already processed images
}
processedImages.add(imageHash);
}
processedImages.add(imageHash);
}
RenderedImage renderedImage = image.getImage();
// Convert to standard RGB colorspace if needed
BufferedImage bufferedImage = convertToRGB(renderedImage, format);
// Write image to zip file
String imageName = filename + "_page_" + pageNum + "_" + count++ + "." + format;
synchronized (zos) {
zos.putNextEntry(new ZipEntry(imageName));
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format, imageBaos);
zos.write(imageBaos.toByteArray());
zos.closeEntry();
}
}
RenderedImage renderedImage = image.getImage();
// Convert to standard RGB colorspace if needed
BufferedImage bufferedImage = convertToRGB(renderedImage, format);
// Write image to zip file
String imageName = filename + "_page_" + pageNum + "_" + count++ + "." + format;
synchronized (zos) {
zos.putNextEntry(new ZipEntry(imageName));
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format, imageBaos);
zos.write(imageBaos.toByteArray());
zos.closeEntry();
}
}
} catch (IOException e) {
ExceptionUtils.logException("image extraction", e);
throw ExceptionUtils.handlePdfException(e, "during image extraction");

View File

@ -381,9 +381,9 @@ public class OCRController {
if (result.getRc() != 0) {
throw ExceptionUtils.createRuntimeException(
"error.commandFailed",
"{0} command failed with exit code: {1}",
null,
"error.commandFailed",
"{0} command failed with exit code: {1}",
null,
"Tesseract",
result.getRc());
}

View File

@ -167,7 +167,10 @@ public class CertSignController {
Boolean showLogo = request.getShowLogo();
if (certType == null) {
throw ExceptionUtils.createIllegalArgumentException("error.optionsNotSpecified", "{0} options are not specified", "certificate type");
throw ExceptionUtils.createIllegalArgumentException(
"error.optionsNotSpecified",
"{0} options are not specified",
"certificate type");
}
KeyStore ks = null;
@ -190,7 +193,10 @@ public class CertSignController {
ks.load(jksfile.getInputStream(), password.toCharArray());
break;
default:
throw ExceptionUtils.createIllegalArgumentException("error.invalidArgument", "Invalid argument: {0}", "certificate type: " + certType);
throw ExceptionUtils.createIllegalArgumentException(
"error.invalidArgument",
"Invalid argument: {0}",
"certificate type: " + certType);
}
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());

View File

@ -150,20 +150,20 @@ public class GetInfoOnPDF {
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
if (pdMetadata != null) {
COSInputStream metaStream = pdMetadata.createInputStream();
// First try to read raw metadata as string to check for standard keywords
byte[] metadataBytes = metaStream.readAllBytes();
String rawMetadata = new String(metadataBytes, StandardCharsets.UTF_8);
if (rawMetadata.contains(standardKeyword)) {
return true;
}
// If raw check doesn't find it, try parsing with XMP parser
// Reset stream for parsing
metaStream.close();
metaStream = pdMetadata.createInputStream();
try {
DomXmpParser domXmpParser = new DomXmpParser();
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
@ -177,7 +177,9 @@ public class GetInfoOnPDF {
}
} catch (XmpParsingException e) {
// XMP parsing failed, but we already checked raw metadata above
log.debug("XMP parsing failed for standard check, but raw metadata was already checked: {}", e.getMessage());
log.debug(
"XMP parsing failed for standard check, but raw metadata was already checked: {}",
e.getMessage());
}
}
} catch (Exception e) {
@ -408,7 +410,7 @@ public class GetInfoOnPDF {
if (pdMetadata != null) {
try {
COSInputStream is = pdMetadata.createInputStream();
try {
DomXmpParser domXmpParser = new DomXmpParser();
XMPMetadata xmpMeta = domXmpParser.parse(is);

View File

@ -43,7 +43,7 @@ public class PasswordController {
MultipartFile fileInput = request.getFileInput();
String password = request.getPassword();
PDDocument document = pdfDocumentFactory.load(fileInput, password);
try {
document.setAllSecurityToBeRemoved(true);
return WebResponseUtils.pdfDocToWebResponse(

View File

@ -83,7 +83,12 @@ public class ValidateSignatureController {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
customCert = (X509Certificate) cf.generateCertificate(certStream);
} catch (CertificateException e) {
throw ExceptionUtils.createRuntimeException("error.invalidFormat", "Invalid {0} format: {1}", e, "certificate file", e.getMessage());
throw ExceptionUtils.createRuntimeException(
"error.invalidFormat",
"Invalid {0} format: {1}",
e,
"certificate file",
e.getMessage());
}
}

View File

@ -264,13 +264,17 @@ public class GeneralWebController {
}
return null;
} catch (Exception e) {
throw ExceptionUtils.createRuntimeException("error.fontLoadingFailed", "Error processing font file", e);
throw ExceptionUtils.createRuntimeException(
"error.fontLoadingFailed",
"Error processing font file",
e);
}
})
.filter(Objects::nonNull)
.toList();
} catch (Exception e) {
throw ExceptionUtils.createRuntimeException("error.fontDirectoryReadFailed", "Failed to read font directory", e);
throw ExceptionUtils.createRuntimeException(
"error.fontDirectoryReadFailed", "Failed to read font directory", e);
}
}

View File

@ -48,7 +48,7 @@ public class ConvertWebsiteToPdfTest {
convertWebsiteToPDF.urlToPdf(request);
});
// Assert
assertEquals("Invalid URL format provided.", thrown.getMessage());
assertEquals("Invalid URL format: provided format is invalid", thrown.getMessage());
}
@Test
@ -66,6 +66,6 @@ public class ConvertWebsiteToPdfTest {
convertWebsiteToPDF.urlToPdf(request);
});
// Assert
assertEquals("URL is not reachable, please provide a valid URL.", thrown.getMessage());
assertEquals("URL is not reachable, please provide a valid URL", thrown.getMessage());
}
}