This commit is contained in:
Anthony Stirling 2025-07-02 14:06:03 +01:00
parent 666fc05ec6
commit 9e3a7c642d
8 changed files with 103 additions and 24 deletions

View File

@ -83,7 +83,7 @@ public class CustomPDFDocumentFactory {
*/ */
public PDDocument load(File file, boolean readOnly) throws IOException { public PDDocument load(File file, boolean readOnly) throws IOException {
if (file == null) { if (file == null) {
throw new IllegalArgumentException("File cannot be null"); throw ExceptionUtils.createNullArgumentException("File");
} }
long fileSize = file.length(); long fileSize = file.length();
@ -110,7 +110,7 @@ public class CustomPDFDocumentFactory {
*/ */
public PDDocument load(Path path, boolean readOnly) throws IOException { public PDDocument load(Path path, boolean readOnly) throws IOException {
if (path == null) { if (path == null) {
throw new IllegalArgumentException("File cannot be null"); throw ExceptionUtils.createNullArgumentException("File");
} }
long fileSize = Files.size(path); long fileSize = Files.size(path);
@ -131,7 +131,7 @@ public class CustomPDFDocumentFactory {
/** Load a PDF from byte array with automatic optimization and read-only option. */ /** Load a PDF from byte array with automatic optimization and read-only option. */
public PDDocument load(byte[] input, boolean readOnly) throws IOException { public PDDocument load(byte[] input, boolean readOnly) throws IOException {
if (input == null) { if (input == null) {
throw new IllegalArgumentException("Input bytes cannot be null"); throw ExceptionUtils.createNullArgumentException("Input bytes");
} }
long dataSize = input.length; long dataSize = input.length;
@ -152,7 +152,7 @@ public class CustomPDFDocumentFactory {
/** Load a PDF from InputStream with automatic optimization and read-only option. */ /** Load a PDF from InputStream with automatic optimization and read-only option. */
public PDDocument load(InputStream input, boolean readOnly) throws IOException { public PDDocument load(InputStream input, boolean readOnly) throws IOException {
if (input == null) { if (input == null) {
throw new IllegalArgumentException("InputStream cannot be null"); throw ExceptionUtils.createNullArgumentException("InputStream");
} }
// Since we don't know the size upfront, buffer to a temp file // Since we don't know the size upfront, buffer to a temp file
@ -175,7 +175,7 @@ public class CustomPDFDocumentFactory {
public PDDocument load(InputStream input, String password, boolean readOnly) public PDDocument load(InputStream input, String password, boolean readOnly)
throws IOException { throws IOException {
if (input == null) { if (input == null) {
throw new IllegalArgumentException("InputStream cannot be null"); throw ExceptionUtils.createNullArgumentException("InputStream");
} }
// Since we don't know the size upfront, buffer to a temp file // Since we don't know the size upfront, buffer to a temp file

View File

@ -137,6 +137,23 @@ public class ExceptionUtils {
return new TranslatableException(message, messageKey, args); return new TranslatableException(message, messageKey, args);
} }
/** Create common validation exceptions with translation support. */
public static IllegalArgumentException createInvalidArgumentException(String argumentName) {
return createIllegalArgumentException(
"error.invalidArgument", "Invalid argument: {0}", argumentName);
}
public static IllegalArgumentException createInvalidArgumentException(
String argumentName, String value) {
return createIllegalArgumentException(
"error.invalidFormat", "Invalid {0} format: {1}", argumentName, value);
}
public static IllegalArgumentException createNullArgumentException(String argumentName) {
return createIllegalArgumentException(
"error.argumentRequired", "{0} must not be null", argumentName);
}
/** Create file validation exceptions. */ /** Create file validation exceptions. */
public static IllegalArgumentException createHtmlFileRequiredException() { public static IllegalArgumentException createHtmlFileRequiredException() {
return createIllegalArgumentException( return createIllegalArgumentException(

View File

@ -571,8 +571,7 @@ public class PdfUtils {
case "less": case "less":
return actualPageCount < pageCount; return actualPageCount < pageCount;
default: default:
throw new IllegalArgumentException( throw ExceptionUtils.createInvalidArgumentException("comparator", comparator);
"Invalid comparator. Only 'greater', 'equal', and 'less' are supported.");
} }
} }

View File

@ -25,6 +25,7 @@ import stirling.software.SPDF.model.api.filter.FileSizeRequest;
import stirling.software.SPDF.model.api.filter.PageRotationRequest; import stirling.software.SPDF.model.api.filter.PageRotationRequest;
import stirling.software.SPDF.model.api.filter.PageSizeRequest; import stirling.software.SPDF.model.api.filter.PageSizeRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.PdfUtils; import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
@ -96,7 +97,7 @@ public class FilterController {
valid = actualPageCount < pageCount; valid = actualPageCount < pageCount;
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw ExceptionUtils.createInvalidArgumentException("comparator", comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
@ -139,7 +140,7 @@ public class FilterController {
valid = actualArea < standardArea; valid = actualArea < standardArea;
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw ExceptionUtils.createInvalidArgumentException("comparator", comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
@ -172,7 +173,7 @@ public class FilterController {
valid = actualFileSize < fileSize; valid = actualFileSize < fileSize;
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw ExceptionUtils.createInvalidArgumentException("comparator", comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
@ -208,7 +209,7 @@ public class FilterController {
valid = actualRotation < rotation; valid = actualRotation < rotation;
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw ExceptionUtils.createInvalidArgumentException("comparator", comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);

View File

@ -225,6 +225,7 @@ error.pathTraversalDetected=Path traversal detected for security reasons.
# Validation messages # Validation messages
error.invalidArgument=Invalid argument: {0} error.invalidArgument=Invalid argument: {0}
error.argumentRequired={0} must not be null
error.operationFailed=Operation failed: {0} error.operationFailed=Operation failed: {0}
error.angleNotMultipleOf90=Angle must be a multiple of 90 error.angleNotMultipleOf90=Angle must be a multiple of 90
error.pdfBookmarksNotFound=No PDF bookmarks/outline found in document error.pdfBookmarksNotFound=No PDF bookmarks/outline found in document

View File

@ -348,19 +348,13 @@
// Handle structured error response with translation support // Handle structured error response with translation support
let displayMessage = json.message; let displayMessage = json.message;
// If translation info is available, use it to translate the message // If translation info is available, use MessageFormatter to translate
if (json.translationKey && window.stirlingPDF && window.stirlingPDF.translations) { if (json.translationKey && window.MessageFormatter) {
const translatedTemplate = window.stirlingPDF.translations[json.translationKey]; displayMessage = window.MessageFormatter.translate(
if (translatedTemplate) { json.translationKey,
displayMessage = translatedTemplate; json.translationArgs,
json.message // fallback to original message
// Replace placeholders with args if available );
if (json.translationArgs && Array.isArray(json.translationArgs)) {
json.translationArgs.forEach((arg, index) => {
displayMessage = displayMessage.replace(`{${index}}`, arg);
});
}
}
} }
showErrorBanner((json.error || 'Error') + ': ' + displayMessage, json.trace || ''); showErrorBanner((json.error || 'Error') + ': ' + displayMessage, json.trace || '');

View File

@ -0,0 +1,66 @@
/**
* Utility for formatting internationalized messages with placeholder replacement.
* Supports the {0}, {1}, {2}... placeholder format used by Java MessageFormat.
*/
window.MessageFormatter = (function() {
'use strict';
/**
* Format a message template by replacing {0}, {1}, etc. placeholders with provided arguments.
*
* @param {string} template - The message template with {0}, {1}, etc. placeholders
* @param {Array|string} args - Arguments to replace placeholders with. Can be array or individual arguments
* @returns {string} The formatted message with placeholders replaced
*
* @example
* formatMessage("Hello {0}, you have {1} messages", ["John", 5])
* // Returns: "Hello John, you have 5 messages"
*
* formatMessage("Error {0}: {1}", "404", "Not Found")
* // Returns: "Error 404: Not Found"
*/
function formatMessage(template, ...args) {
if (!template || typeof template !== 'string') {
return template || '';
}
// Handle case where first argument is an array
const argumentArray = Array.isArray(args[0]) ? args[0] : args;
// Replace {0}, {1}, {2}, etc. with corresponding arguments
return template.replace(/\{(\d+)\}/g, function(match, index) {
const argIndex = parseInt(index, 10);
return argumentArray[argIndex] !== undefined && argumentArray[argIndex] !== null
? String(argumentArray[argIndex])
: match; // Keep original placeholder if no argument provided
});
}
/**
* Translate and format an error message using the global translation object.
* Falls back to the provided fallback message if translation not found.
*
* @param {string} translationKey - The translation key (e.g., "error.dpiExceedsLimit")
* @param {Array} translationArgs - Arguments for placeholder replacement
* @param {string} fallbackMessage - Fallback message if translation not found
* @returns {string} The translated and formatted message
*/
function translateAndFormat(translationKey, translationArgs, fallbackMessage) {
if (!window.stirlingPDF || !window.stirlingPDF.translations) {
return fallbackMessage || translationKey;
}
const template = window.stirlingPDF.translations[translationKey];
if (!template) {
return fallbackMessage || translationKey;
}
return formatMessage(template, translationArgs || []);
}
// Public API
return {
format: formatMessage,
translate: translateAndFormat
};
})();

View File

@ -86,6 +86,7 @@
<script th:src="@{'/js/tab-container.js'}"></script> <script th:src="@{'/js/tab-container.js'}"></script>
<script th:src="@{'/js/darkmode.js'}"></script> <script th:src="@{'/js/darkmode.js'}"></script>
<script th:src="@{'/js/csrf.js'}"></script> <script th:src="@{'/js/csrf.js'}"></script>
<script th:src="@{'/js/messageFormatter.js'}"></script>
<script th:inline="javascript"> <script th:inline="javascript">
function UpdatePosthogConsent(){ function UpdatePosthogConsent(){