mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
feat(docker): update base images to Java 25, Spring 4, Jackson 3, Gradle 9 and optimize JVM options (Project Lilliput) (#5725)
Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
co-authored by
Anthony Stirling
parent
24128dd318
commit
1f9b90ad57
+20
-4
@@ -46,8 +46,23 @@ dependencies {
|
||||
|
||||
implementation project(':common')
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jetty'
|
||||
implementation 'com.posthog.java:posthog:1.2.0'
|
||||
implementation 'org.telegram:telegrambots:6.9.7.1'
|
||||
implementation 'org.eclipse.jetty.http2:jetty-http2-server'
|
||||
implementation ('org.telegram:telegrambots:6.9.7.1') {
|
||||
// Grizzly server + Jersey JAX-RS stack: only used for webhook mode;
|
||||
// Stirling-PDF uses long-polling mode so these are dead weight (~3 MB)
|
||||
exclude group: 'org.glassfish.jersey.inject'
|
||||
exclude group: 'org.glassfish.jersey.media'
|
||||
exclude group: 'org.glassfish.jersey.containers'
|
||||
exclude group: 'org.glassfish.jersey.core'
|
||||
exclude group: 'org.glassfish.jersey.ext'
|
||||
exclude group: 'org.glassfish.grizzly'
|
||||
exclude group: 'org.glassfish.hk2'
|
||||
exclude group: 'org.glassfish.hk2.external'
|
||||
exclude group: 'org.javassist', module: 'javassist'
|
||||
// Old javax JAX-RS Jackson bindings (not needed, project uses jakarta)
|
||||
exclude group: 'com.fasterxml.jackson.jaxrs'
|
||||
exclude group: 'com.fasterxml.jackson.module', module: 'jackson-module-jaxb-annotations'
|
||||
}
|
||||
implementation 'commons-io:commons-io:2.21.0'
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
@@ -78,8 +93,9 @@ dependencies {
|
||||
implementation 'com.opencsv:opencsv:5.12.0' // https://mvnrepository.com/artifact/com.opencsv/opencsv
|
||||
implementation 'org.apache.poi:poi-ooxml:5.5.1'
|
||||
|
||||
// Batik
|
||||
implementation 'org.apache.xmlgraphics:batik-all:1.19'
|
||||
// Batik only bridge module needed (transitively pulls anim, gvt, util, css, dom, svg-dom)
|
||||
// Replaces batik-all which included unused codec, svggen, transcoder, script modules
|
||||
implementation 'org.apache.xmlgraphics:batik-bridge:1.19'
|
||||
|
||||
// PDFBox Graphics2D bridge for Batik SVG to PDF conversion
|
||||
implementation 'de.rototor.pdfbox:graphics2d:3.0.5'
|
||||
|
||||
@@ -49,8 +49,8 @@ public class LibreOfficeListener {
|
||||
process = SystemCommand.runCommand(Runtime.getRuntime(), "unoconv --listener");
|
||||
lastActivityTime = System.currentTimeMillis();
|
||||
|
||||
// Start a background thread to monitor the activity timeout
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
// Start a virtual thread to monitor the activity timeout
|
||||
executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||
executorService.submit(
|
||||
() -> {
|
||||
while (true) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.context.WebServerInitializedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@@ -176,11 +176,14 @@ public class SPDFApplication {
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onWebServerInitialized(WebServerInitializedEvent event) {
|
||||
int actualPort = event.getWebServer().getPort();
|
||||
serverPortStatic = String.valueOf(actualPort);
|
||||
public void onApplicationReady(ApplicationReadyEvent event) {
|
||||
String port =
|
||||
event.getApplicationContext().getEnvironment().getProperty("local.server.port");
|
||||
if (port != null) {
|
||||
serverPortStatic = port;
|
||||
}
|
||||
// Log the actual runtime port for Tauri to parse
|
||||
log.info("Stirling-PDF running on port: {}", actualPort);
|
||||
log.info("Stirling-PDF running on port: {}", serverPortStatic);
|
||||
}
|
||||
|
||||
private static void printStartupLogs() {
|
||||
|
||||
@@ -51,9 +51,7 @@ public class ExternalAppDepConfig {
|
||||
*/
|
||||
private final Map<String, List<String>> commandToGroupMapping;
|
||||
|
||||
private final ExecutorService pool =
|
||||
Executors.newFixedThreadPool(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors() / 2));
|
||||
private final ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor();
|
||||
|
||||
public ExternalAppDepConfig(
|
||||
EndpointConfiguration endpointConfiguration, RuntimePathConfig runtimePathConfig) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.MultipartConfigFactory;
|
||||
import org.springframework.boot.servlet.MultipartConfigFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
@@ -54,8 +54,8 @@ public class TauriProcessMonitor {
|
||||
scheduler =
|
||||
Executors.newSingleThreadScheduledExecutor(
|
||||
r -> {
|
||||
Thread t = new Thread(r, "tauri-process-monitor");
|
||||
t.setDaemon(true);
|
||||
Thread t =
|
||||
Thread.ofVirtual().name("tauri-process-monitor").unstarted(r);
|
||||
return t;
|
||||
});
|
||||
|
||||
|
||||
@@ -154,8 +154,8 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
.maxAge(3600);
|
||||
} else {
|
||||
// Default to allowing all origins when nothing is configured
|
||||
logger.info(
|
||||
"No CORS allowed origins configured in settings.yml (system.corsAllowedOrigins); allowing all origins.");
|
||||
logger.debug(
|
||||
"No CORS allowed origins configured in settings.yml (system.corsAllowedOrigins); WebMvcConfig allowing all origins.");
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
|
||||
|
||||
+3
-3
@@ -15,9 +15,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import lombok.Getter;
|
||||
@@ -32,6 +29,9 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@GeneralApi
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -16,9 +16,6 @@ import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import lombok.Data;
|
||||
@@ -35,6 +32,9 @@ import stirling.software.common.service.UserServiceInterface;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Slf4j
|
||||
@UiDataApi
|
||||
public class UIDataController {
|
||||
@@ -44,18 +44,21 @@ public class UIDataController {
|
||||
private final UserServiceInterface userService;
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public UIDataController(
|
||||
ApplicationProperties applicationProperties,
|
||||
SharedSignatureService signatureService,
|
||||
@Autowired(required = false) UserServiceInterface userService,
|
||||
ResourceLoader resourceLoader,
|
||||
RuntimePathConfig runtimePathConfig) {
|
||||
RuntimePathConfig runtimePathConfig,
|
||||
ObjectMapper objectMapper) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
this.signatureService = signatureService;
|
||||
this.userService = userService;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.runtimePathConfig = runtimePathConfig;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/footer-info")
|
||||
@@ -93,9 +96,8 @@ public class UIDataController {
|
||||
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Map<String, List<Dependency>> licenseData =
|
||||
mapper.readValue(json, new TypeReference<>() {});
|
||||
objectMapper.readValue(json, new TypeReference<>() {});
|
||||
data.setDependencies(licenseData.get("dependencies"));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load licenses data", e);
|
||||
@@ -127,8 +129,8 @@ public class UIDataController {
|
||||
|
||||
for (String config : pipelineConfigs) {
|
||||
Map<String, Object> jsonContent =
|
||||
new ObjectMapper()
|
||||
.readValue(config, new TypeReference<Map<String, Object>>() {});
|
||||
objectMapper.readValue(
|
||||
config, new TypeReference<Map<String, Object>>() {});
|
||||
String name = (String) jsonContent.get("name");
|
||||
if (name == null || name.length() < 1) {
|
||||
String filename =
|
||||
|
||||
+36
-19
@@ -91,22 +91,32 @@ public class ConvertOfficeController {
|
||||
|
||||
Path libreOfficeProfile = null;
|
||||
try {
|
||||
ProcessExecutorResult result;
|
||||
// Run Unoconvert command
|
||||
if (isUnoconvertAvailable()) {
|
||||
// Unoconvert: schreibe direkt in outputPath innerhalb des workDir
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(runtimePathConfig.getUnoConvertPath());
|
||||
command.add("--convert-to");
|
||||
command.add("pdf");
|
||||
command.add(inputPath.toString());
|
||||
command.add(outputPath.toString());
|
||||
ProcessExecutorResult result = null;
|
||||
IOException unoconvertException = null;
|
||||
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
} // Run soffice command
|
||||
else {
|
||||
// Try unoconvert first if available
|
||||
if (isUnoconvertAvailable()) {
|
||||
try {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(runtimePathConfig.getUnoConvertPath());
|
||||
command.add("--convert-to");
|
||||
command.add("pdf");
|
||||
command.add(inputPath.toString());
|
||||
command.add(outputPath.toString());
|
||||
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
} catch (IOException e) {
|
||||
unoconvertException = e;
|
||||
log.warn(
|
||||
"Unoconvert command failed ({}). Falling back to soffice command.",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to soffice if unoconvert was unavailable or failed
|
||||
if (result == null) {
|
||||
libreOfficeProfile = Files.createTempDirectory("libreoffice_profile_");
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(runtimePathConfig.getSOfficePath());
|
||||
@@ -114,14 +124,21 @@ public class ConvertOfficeController {
|
||||
command.add("--headless");
|
||||
command.add("--nologo");
|
||||
command.add("--convert-to");
|
||||
command.add("pdf:writer_pdf_Export");
|
||||
command.add("pdf");
|
||||
command.add("--outdir");
|
||||
command.add(workDir.toString());
|
||||
command.add(inputPath.toString());
|
||||
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
try {
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
} catch (IOException e) {
|
||||
if (unoconvertException != null) {
|
||||
e.addSuppressed(unoconvertException);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the result
|
||||
|
||||
+1
-4
@@ -70,10 +70,7 @@ public class ConvertPDFToExcelController {
|
||||
tables.size() == 1
|
||||
? String.format(Locale.ROOT, "Page %d", pageNum)
|
||||
: String.format(
|
||||
Locale.ROOT,
|
||||
"Page %d Table %d",
|
||||
pageNum,
|
||||
tableIdx + 1);
|
||||
Locale.ROOT, "Page %d Table %d", pageNum, tableIdx + 1);
|
||||
|
||||
sheetName = getUniqueSheetName(workbook, sheetName);
|
||||
Sheet sheet = workbook.createSheet(sheetName);
|
||||
|
||||
+2
-2
@@ -9,13 +9,13 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.exception.CacheUnavailableException;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@ControllerAdvice(assignableTypes = ConvertPdfJsonController.class)
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
|
||||
+3
-2
@@ -19,8 +19,6 @@ import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.opencsv.CSVWriter;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
@@ -38,6 +36,9 @@ import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.FormUtils;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/form")
|
||||
@Tag(
|
||||
|
||||
+16
-17
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.form;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -8,13 +7,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.FormUtils;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
final class FormPayloadParser {
|
||||
|
||||
private static final String KEY_FIELDS = "fields";
|
||||
@@ -32,8 +32,7 @@ final class FormPayloadParser {
|
||||
|
||||
private FormPayloadParser() {}
|
||||
|
||||
static Map<String, Object> parseValueMap(ObjectMapper objectMapper, String json)
|
||||
throws IOException {
|
||||
static Map<String, Object> parseValueMap(ObjectMapper objectMapper, String json) {
|
||||
if (json == null || json.isBlank()) {
|
||||
return Map.of();
|
||||
}
|
||||
@@ -41,7 +40,7 @@ final class FormPayloadParser {
|
||||
JsonNode root;
|
||||
try {
|
||||
root = objectMapper.readTree(json);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
// Fallback to legacy direct map parse (will throw again if invalid)
|
||||
return objectMapper.readValue(json, MAP_TYPE);
|
||||
}
|
||||
@@ -88,14 +87,14 @@ final class FormPayloadParser {
|
||||
}
|
||||
|
||||
static List<FormUtils.ModifyFormFieldDefinition> parseModificationDefinitions(
|
||||
ObjectMapper objectMapper, String json) throws IOException {
|
||||
ObjectMapper objectMapper, String json) {
|
||||
if (json == null || json.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return objectMapper.readValue(json, MODIFY_FIELD_LIST_TYPE);
|
||||
}
|
||||
|
||||
static List<String> parseNameList(ObjectMapper objectMapper, String json) throws IOException {
|
||||
static List<String> parseNameList(ObjectMapper objectMapper, String json) {
|
||||
if (json == null || json.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -119,7 +118,7 @@ final class FormPayloadParser {
|
||||
}
|
||||
}
|
||||
} else if (root.isTextual()) {
|
||||
final String single = trimToNull(root.asText());
|
||||
final String single = trimToNull(root.asText(""));
|
||||
if (single != null) {
|
||||
names.add(single);
|
||||
}
|
||||
@@ -131,7 +130,7 @@ final class FormPayloadParser {
|
||||
|
||||
try {
|
||||
return objectMapper.readValue(json, STRING_LIST_TYPE);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidFormat",
|
||||
"Invalid {0} format: {1}",
|
||||
@@ -199,7 +198,7 @@ final class FormPayloadParser {
|
||||
return null;
|
||||
}
|
||||
if (node.isTextual()) {
|
||||
return trimToEmpty(node.asText());
|
||||
return trimToEmpty(node.asText(""));
|
||||
}
|
||||
if (node.isNumber()) {
|
||||
return node.numberValue().toString();
|
||||
@@ -208,7 +207,7 @@ final class FormPayloadParser {
|
||||
return Boolean.toString(node.booleanValue());
|
||||
}
|
||||
// Fallback for other scalar-like nodes
|
||||
return trimToEmpty(node.asText());
|
||||
return trimToEmpty(node.asText(""));
|
||||
}
|
||||
|
||||
private static void collectNames(JsonNode arrayNode, Set<String> sink) {
|
||||
@@ -229,7 +228,7 @@ final class FormPayloadParser {
|
||||
}
|
||||
|
||||
if (node.isTextual()) {
|
||||
return trimToNull(node.asText());
|
||||
return trimToNull(node.asText(""));
|
||||
}
|
||||
|
||||
if (node.isObject()) {
|
||||
@@ -264,8 +263,8 @@ final class FormPayloadParser {
|
||||
private static Map<String, Object> objectToLinkedMap(JsonNode objectNode) {
|
||||
final Map<String, Object> result = new LinkedHashMap<>();
|
||||
objectNode
|
||||
.fieldNames()
|
||||
.forEachRemaining(
|
||||
.propertyNames()
|
||||
.forEach(
|
||||
key -> {
|
||||
final JsonNode v = objectNode.get(key);
|
||||
if (v == null || v.isNull()) {
|
||||
|
||||
+2
-3
@@ -82,9 +82,8 @@ public class ExtractImagesController {
|
||||
Set<byte[]> processedImages = new HashSet<>();
|
||||
|
||||
if (useMultithreading) {
|
||||
// Executor service to handle multithreading
|
||||
ExecutorService executor =
|
||||
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
// Virtual thread executor — lightweight threads ideal for I/O-bound image extraction
|
||||
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
Set<Future<Void>> futures = new HashSet<>();
|
||||
|
||||
// Safely iterate over each page, handling corrupt PDFs where page count might be wrong
|
||||
|
||||
+20
-5
@@ -246,10 +246,12 @@ public class OCRController {
|
||||
command.add("--clean-final");
|
||||
}
|
||||
if (ocrType != null && !ocrType.isEmpty()) {
|
||||
if ("skip-text".equals(ocrType)) {
|
||||
command.add("--skip-text");
|
||||
} else if ("force-ocr".equals(ocrType)) {
|
||||
if ("force-ocr".equals(ocrType)) {
|
||||
command.add("--force-ocr");
|
||||
} else {
|
||||
// Default for 'Normal' and 'skip-text': use --skip-text
|
||||
// ocrmypdf 17+ requires explicit flag when PDF already contains text
|
||||
command.add("--skip-text");
|
||||
}
|
||||
}
|
||||
command.add("--invalidate-digital-signatures");
|
||||
@@ -378,11 +380,12 @@ public class OCRController {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("tesseract");
|
||||
command.add(imagePath.toString());
|
||||
command.add(
|
||||
String outputBase =
|
||||
new File(
|
||||
tempOutputDir,
|
||||
String.format(Locale.ROOT, "page_%d", pageNum))
|
||||
.toString());
|
||||
.toString();
|
||||
command.add(outputBase);
|
||||
command.add("-l");
|
||||
command.add(String.join("+", selectedLanguages));
|
||||
command.add("pdf"); // Always output PDF
|
||||
@@ -400,6 +403,18 @@ public class OCRController {
|
||||
result.getRc());
|
||||
}
|
||||
|
||||
// Verify the OCR'd PDF was created
|
||||
if (!pageOutputPath.exists()) {
|
||||
log.warn(
|
||||
"Tesseract did not create expected output file: {}. Page may be blank or unreadable.",
|
||||
pageOutputPath.getAbsolutePath());
|
||||
// Save original page without OCR as fallback
|
||||
try (PDDocument pageDoc = new PDDocument()) {
|
||||
pageDoc.addPage(page);
|
||||
pageDoc.save(pageOutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Add OCR'd PDF to merger
|
||||
merger.addSource(pageOutputPath);
|
||||
} else {
|
||||
|
||||
+5
-5
@@ -14,10 +14,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -34,6 +30,10 @@ import stirling.software.common.service.PostHogService;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.DatabindException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@PipelineApi
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -54,7 +54,7 @@ public class PipelineController {
|
||||
+ "Users provide files and a JSON configuration defining the sequence of operations to perform. "
|
||||
+ "Input:PDF Output:PDF/ZIP Type:MIMO")
|
||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
||||
throws JsonMappingException, JsonProcessingException {
|
||||
throws DatabindException, JacksonException {
|
||||
MultipartFile[] files = request.getFileInput();
|
||||
String jsonString = request.getJson();
|
||||
if (files == null) {
|
||||
|
||||
+2
-2
@@ -28,8 +28,6 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.PipelineConfig;
|
||||
@@ -40,6 +38,8 @@ import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.service.PostHogService;
|
||||
import stirling.software.common.util.FileMonitor;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PipelineDirectoryProcessor {
|
||||
|
||||
+6
-5
@@ -47,10 +47,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -66,6 +62,11 @@ import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
@SecurityApi
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -77,7 +78,7 @@ public class GetInfoOnPDF {
|
||||
private static final String PAGE_PREFIX = "Page ";
|
||||
private static final long MAX_FILE_SIZE = 100L * 1024 * 1024;
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static final ObjectMapper objectMapper = JsonMapper.builder().build();
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final VeraPDFService veraPDFService;
|
||||
|
||||
+20
-1
@@ -1,5 +1,7 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
@@ -54,10 +56,27 @@ public class MetricsController {
|
||||
}
|
||||
Map<String, String> status = new HashMap<>();
|
||||
status.put("status", "UP");
|
||||
status.put("version", getClass().getPackage().getImplementationVersion());
|
||||
String version = getClass().getPackage().getImplementationVersion();
|
||||
if (version == null) {
|
||||
version = getVersionFromProperties();
|
||||
}
|
||||
status.put("version", version);
|
||||
return ResponseEntity.ok(status);
|
||||
}
|
||||
|
||||
private String getVersionFromProperties() {
|
||||
try (InputStream is = getClass().getResourceAsStream("/version.properties")) {
|
||||
if (is != null) {
|
||||
Properties props = new Properties();
|
||||
props.load(is);
|
||||
return props.getProperty("version");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load version.properties", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@GetMapping("/load")
|
||||
@Operation(
|
||||
summary = "GET request count",
|
||||
|
||||
@@ -190,6 +190,31 @@ public class GlobalExceptionHandler {
|
||||
return problemDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given IOException indicates that the client disconnected before the
|
||||
* response could be written (broken pipe, connection reset, etc.). When this happens there is
|
||||
* no point in serialising a {@link ProblemDetail} body because the socket is already closed —
|
||||
* and attempting to do so may trigger a secondary {@code HttpMessageNotWritableException} if
|
||||
* the response Content-Type was already committed as a non-JSON type (e.g. image/png).
|
||||
*/
|
||||
private static boolean isClientDisconnectException(IOException ex) {
|
||||
// Walk the causal chain — Jetty/Tomcat may wrap the low-level SocketException
|
||||
Throwable current = ex;
|
||||
while (current != null) {
|
||||
String msg = current.getMessage();
|
||||
if (msg != null) {
|
||||
String lower = msg.toLowerCase(java.util.Locale.ROOT);
|
||||
if (lower.contains("broken pipe")
|
||||
|| lower.contains("connection reset")
|
||||
|| lower.contains("an established connection was aborted")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a standardized ProblemDetail response for exceptions with error
|
||||
* codes.
|
||||
@@ -1107,6 +1132,15 @@ public class GlobalExceptionHandler {
|
||||
public ResponseEntity<ProblemDetail> handleIOException(
|
||||
IOException ex, HttpServletRequest request) {
|
||||
|
||||
// Broken pipe / connection reset means the client disconnected.
|
||||
// Attempting to write a ProblemDetail response will fail because the
|
||||
// response Content-Type may already be committed (e.g. image/png) and
|
||||
// the client is gone anyway. Log at WARN and return an empty body.
|
||||
if (isClientDisconnectException(ex)) {
|
||||
log.warn("Client disconnected at {}: {}", request.getRequestURI(), ex.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
|
||||
// Check if this is a PDF-specific error and wrap it appropriately
|
||||
IOException processedException =
|
||||
ExceptionUtils.handlePdfException(ex, request.getRequestURI());
|
||||
|
||||
@@ -3,10 +3,10 @@ package stirling.software.SPDF.model;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import tools.jackson.databind.JsonNode;
|
||||
|
||||
public class ApiEndpoint {
|
||||
private final String name;
|
||||
private Map<String, JsonNode> parameters;
|
||||
@@ -18,10 +18,10 @@ public class ApiEndpoint {
|
||||
postNode.path("parameters")
|
||||
.forEach(
|
||||
paramNode -> {
|
||||
String paramName = paramNode.path("name").asText();
|
||||
String paramName = paramNode.path("name").asText("");
|
||||
parameters.put(paramName, paramNode);
|
||||
});
|
||||
this.description = postNode.path("description").asText();
|
||||
this.description = postNode.path("description").asText("");
|
||||
}
|
||||
|
||||
public boolean areParametersValid(Map<String, Object> providedParams) {
|
||||
|
||||
@@ -15,9 +15,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -28,6 +25,9 @@ import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.common.service.UserServiceInterface;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiDocService {
|
||||
@@ -36,12 +36,15 @@ public class ApiDocService {
|
||||
|
||||
private final ServletContext servletContext;
|
||||
private final UserServiceInterface userService;
|
||||
private final ObjectMapper objectMapper;
|
||||
Map<String, List<String>> outputToFileTypes = new HashMap<>();
|
||||
JsonNode apiDocsJsonRootNode;
|
||||
|
||||
public ApiDocService(
|
||||
ObjectMapper objectMapper,
|
||||
ServletContext servletContext,
|
||||
@Autowired(required = false) UserServiceInterface userService) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.servletContext = servletContext;
|
||||
this.userService = userService;
|
||||
}
|
||||
@@ -116,8 +119,7 @@ public class ApiDocService {
|
||||
ResponseEntity<String> response =
|
||||
restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class);
|
||||
apiDocsJson = response.getBody();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
apiDocsJsonRootNode = mapper.readTree(apiDocsJson);
|
||||
apiDocsJsonRootNode = objectMapper.readTree(apiDocsJson);
|
||||
JsonNode paths = apiDocsJsonRootNode.path("paths");
|
||||
paths.propertyStream()
|
||||
.forEach(
|
||||
|
||||
+2
-1
@@ -427,7 +427,8 @@ public class CertificateValidationService {
|
||||
try (InputStream certStream =
|
||||
getClass().getClassLoader().getResourceAsStream("certs/cacert.pem")) {
|
||||
if (certStream == null) {
|
||||
log.warn("Bundled Mozilla CA certificate file not found in resources");
|
||||
log.debug(
|
||||
"Bundled Mozilla CA certificate file not found in resources — using Java system trust store only");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +90,6 @@ import org.apache.pdfbox.util.Matrix;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -129,6 +127,8 @@ import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -6834,7 +6834,8 @@ public class PdfJsonConversionService {
|
||||
|
||||
/** Schedules automatic cleanup of cached documents after 30 minutes. */
|
||||
private void scheduleDocumentCleanup(String jobId) {
|
||||
new Thread(
|
||||
Thread.ofVirtual()
|
||||
.start(
|
||||
() -> {
|
||||
try {
|
||||
Thread.sleep(TimeUnit.MINUTES.toMillis(30));
|
||||
@@ -6843,7 +6844,6 @@ public class PdfJsonConversionService {
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import java.util.stream.Stream;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.SignatureFile;
|
||||
@@ -24,6 +22,8 @@ import stirling.software.SPDF.model.api.signature.SavedSignatureRequest;
|
||||
import stirling.software.SPDF.model.api.signature.SavedSignatureResponse;
|
||||
import stirling.software.common.configuration.InstallationPathConfig;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SharedSignatureService {
|
||||
@@ -33,9 +33,9 @@ public class SharedSignatureService {
|
||||
private final String ALL_USERS_FOLDER = "ALL_USERS";
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public SharedSignatureService() {
|
||||
public SharedSignatureService(ObjectMapper objectMapper) {
|
||||
SIGNATURE_BASE_PATH = InstallationPathConfig.getSignaturesPath();
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public boolean hasAccessToFile(String username, String fileName) throws IOException {
|
||||
|
||||
+5
-5
@@ -18,8 +18,6 @@ import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -38,6 +36,8 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.service.TaskManager;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Service for lazy loading PDF pages. Caches PDF documents and extracts pages on-demand to reduce
|
||||
* memory usage for large PDFs.
|
||||
@@ -255,7 +255,8 @@ public class PdfLazyLoadingService {
|
||||
|
||||
/** Schedules automatic cleanup of cached documents after 30 minutes. */
|
||||
private void scheduleDocumentCleanup(String jobId) {
|
||||
new Thread(
|
||||
Thread.ofVirtual()
|
||||
.start(
|
||||
() -> {
|
||||
try {
|
||||
Thread.sleep(TimeUnit.MINUTES.toMillis(30));
|
||||
@@ -264,8 +265,7 @@ public class PdfLazyLoadingService {
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+23
-9
@@ -18,14 +18,14 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.service.pdfjson.type3.Type3FontSignatureCalculator;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -202,20 +202,34 @@ public class Type3FontLibrary {
|
||||
if (payload == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] data = null;
|
||||
String base64;
|
||||
if (payload.base64 != null && !payload.base64.isBlank()) {
|
||||
// Validate the base64 string without wasteful full decode
|
||||
// Only decode a small prefix to verify encoding is valid
|
||||
try {
|
||||
data = Base64.getDecoder().decode(payload.base64);
|
||||
byte[] probe =
|
||||
Base64.getDecoder()
|
||||
.decode(
|
||||
payload.base64.substring(
|
||||
0, Math.min(4, payload.base64.length())));
|
||||
if (probe.length == 0 && payload.base64.length() <= 4) {
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.warn("[TYPE3] Invalid base64 payload in Type3 library: {}", ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
// Keep the original base64 string directly — avoids 3x memory pressure
|
||||
base64 = payload.base64;
|
||||
} else if (payload.resource != null && !payload.resource.isBlank()) {
|
||||
data = loadResourceBytes(payload.resource);
|
||||
}
|
||||
if (data == null || data.length == 0) {
|
||||
byte[] data = loadResourceBytes(payload.resource);
|
||||
if (data == null || data.length == 0) {
|
||||
return null;
|
||||
}
|
||||
base64 = Base64.getEncoder().encodeToString(data);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
String base64 = Base64.getEncoder().encodeToString(data);
|
||||
return new Type3FontLibraryPayload(base64, normalizeFormat(payload.format));
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -26,14 +26,15 @@ import org.apache.pdfbox.pdmodel.font.PDType3Font;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import stirling.software.SPDF.service.pdfjson.type3.Type3FontSignatureCalculator;
|
||||
import stirling.software.SPDF.service.pdfjson.type3.Type3GlyphExtractor;
|
||||
import stirling.software.SPDF.service.pdfjson.type3.model.Type3GlyphOutline;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectWriter;
|
||||
import tools.jackson.databind.SerializationFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
/**
|
||||
* Small CLI helper that scans a PDF for Type3 fonts, computes their signatures, and optionally
|
||||
* emits JSON describing the glyph coverage. This allows Type3 library entries to be added without
|
||||
@@ -48,7 +49,7 @@ import stirling.software.SPDF.service.pdfjson.type3.model.Type3GlyphOutline;
|
||||
public final class Type3SignatureTool {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER =
|
||||
new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
|
||||
JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||
|
||||
private Type3SignatureTool() {}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ public class SvgToPdf {
|
||||
private GraphicsNode buildGvtWithTimeout(BridgeContext ctx, SVGDocument svgDoc)
|
||||
throws IOException {
|
||||
GVTBuilder builder = new GVTBuilder();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
|
||||
Callable<GraphicsNode> buildTask = () -> builder.build(ctx, svgDoc);
|
||||
Future<GraphicsNode> future = executor.submit(buildTask);
|
||||
|
||||
@@ -13,6 +13,18 @@ logging.level.stirling.software.common.service.JobExecutorService=INFO
|
||||
logging.level.stirling.software.common.service.TaskManager=INFO
|
||||
spring.jpa.open-in-view=false
|
||||
server.forward-headers-strategy=NATIVE
|
||||
|
||||
# Enable HTTP/2 for improved performance (multiplexed streams, header compression)
|
||||
server.http2.enabled=true
|
||||
|
||||
# Enable virtual threads (Java 21+, pinning fix in Java 25)
|
||||
spring.threads.virtual.enabled=true
|
||||
|
||||
# Response compression
|
||||
server.compression.enabled=true
|
||||
server.compression.min-response-size=1024
|
||||
server.compression.mime-types=application/json,application/xml,text/html,text/plain,text/css,application/javascript
|
||||
|
||||
server.error.path=/error
|
||||
server.error.whitelabel.enabled=false
|
||||
server.error.include-stacktrace=always
|
||||
@@ -38,7 +50,7 @@ spring.devtools.livereload.enabled=true
|
||||
spring.devtools.restart.exclude=stirling.software.proprietary.security/**
|
||||
spring.web.resources.mime-mappings.webmanifest=application/manifest+json
|
||||
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
|
||||
server.tomcat.max-http-header-size=32768
|
||||
server.jetty.max-http-request-header-size=32768
|
||||
|
||||
spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
|
||||
+3
-3
@@ -27,13 +27,13 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.SPDF.controller.api.EditTableOfContentsController.BookmarkItem;
|
||||
import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class EditTableOfContentsControllerTest {
|
||||
|
||||
|
||||
+18
-16
@@ -40,14 +40,15 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.PDFVerificationResult;
|
||||
import stirling.software.SPDF.service.VeraPDFService;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
@DisplayName("GetInfoOnPDF Controller Tests")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GetInfoOnPDFTest {
|
||||
@@ -64,7 +65,7 @@ class GetInfoOnPDFTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
objectMapper = new ObjectMapper();
|
||||
objectMapper = JsonMapper.builder().build();
|
||||
}
|
||||
|
||||
/** Helper method to load a PDF file from test resources */
|
||||
@@ -216,8 +217,8 @@ class GetInfoOnPDFTest {
|
||||
Assertions.assertTrue(jsonNode.has("Permissions"));
|
||||
|
||||
JsonNode metadata = jsonNode.get("Metadata");
|
||||
Assertions.assertEquals("Test Title", metadata.get("Title").asText());
|
||||
Assertions.assertEquals("Test Author", metadata.get("Author").asText());
|
||||
Assertions.assertEquals("Test Title", metadata.get("Title").asText(""));
|
||||
Assertions.assertEquals("Test Author", metadata.get("Author").asText(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,12 +316,12 @@ class GetInfoOnPDFTest {
|
||||
JsonNode jsonNode = objectMapper.readTree(jsonResponse);
|
||||
JsonNode metadata = jsonNode.get("Metadata");
|
||||
|
||||
Assertions.assertEquals("Test Title", metadata.get("Title").asText());
|
||||
Assertions.assertEquals("Test Author", metadata.get("Author").asText());
|
||||
Assertions.assertEquals("Test Subject", metadata.get("Subject").asText());
|
||||
Assertions.assertEquals("test, pdf, metadata", metadata.get("Keywords").asText());
|
||||
Assertions.assertEquals("Test Creator", metadata.get("Creator").asText());
|
||||
Assertions.assertEquals("Test Producer", metadata.get("Producer").asText());
|
||||
Assertions.assertEquals("Test Title", metadata.get("Title").asText(""));
|
||||
Assertions.assertEquals("Test Author", metadata.get("Author").asText(""));
|
||||
Assertions.assertEquals("Test Subject", metadata.get("Subject").asText(""));
|
||||
Assertions.assertEquals("test, pdf, metadata", metadata.get("Keywords").asText(""));
|
||||
Assertions.assertEquals("Test Creator", metadata.get("Creator").asText(""));
|
||||
Assertions.assertEquals("Test Producer", metadata.get("Producer").asText(""));
|
||||
Assertions.assertTrue(metadata.has("CreationDate"));
|
||||
Assertions.assertTrue(metadata.has("ModificationDate"));
|
||||
|
||||
@@ -512,10 +513,10 @@ class GetInfoOnPDFTest {
|
||||
JsonNode page1 = perPageInfo.get("Page 1");
|
||||
Assertions.assertTrue(page1.has("Size"));
|
||||
Assertions.assertTrue(page1.get("Size").has("Standard Page"));
|
||||
Assertions.assertEquals("A4", page1.get("Size").get("Standard Page").asText());
|
||||
Assertions.assertEquals("A4", page1.get("Size").get("Standard Page").asText(""));
|
||||
|
||||
JsonNode page2 = perPageInfo.get("Page 2");
|
||||
Assertions.assertEquals("Letter", page2.get("Size").get("Standard Page").asText());
|
||||
Assertions.assertEquals("Letter", page2.get("Size").get("Standard Page").asText(""));
|
||||
|
||||
loadedDoc.close();
|
||||
}
|
||||
@@ -569,7 +570,8 @@ class GetInfoOnPDFTest {
|
||||
JsonNode jsonNode = objectMapper.readTree(jsonResponse);
|
||||
|
||||
Assertions.assertTrue(jsonNode.has("error"));
|
||||
Assertions.assertTrue(jsonNode.get("error").asText().contains("PDF file is required"));
|
||||
Assertions.assertTrue(
|
||||
jsonNode.get("error").asText("").contains("PDF file is required"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -645,7 +647,7 @@ class GetInfoOnPDFTest {
|
||||
|
||||
Assertions.assertTrue(jsonNode.has("error"));
|
||||
Assertions.assertTrue(
|
||||
jsonNode.get("error").asText().contains("exceeds maximum allowed size"));
|
||||
jsonNode.get("error").asText("").contains("exceeds maximum allowed size"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,15 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
class ApiEndpointTest {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final ObjectMapper mapper = JsonMapper.builder().build();
|
||||
|
||||
private JsonNode postNodeWithParams(String description, String... names) {
|
||||
ObjectNode post = mapper.createObjectNode();
|
||||
|
||||
@@ -12,14 +12,15 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
||||
import stirling.software.SPDF.model.ApiEndpoint;
|
||||
import stirling.software.common.service.UserServiceInterface;
|
||||
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ApiDocServiceTest {
|
||||
|
||||
@@ -27,11 +28,11 @@ class ApiDocServiceTest {
|
||||
@Mock UserServiceInterface userService;
|
||||
|
||||
ApiDocService apiDocService;
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ObjectMapper mapper = JsonMapper.builder().build();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
apiDocService = new ApiDocService(servletContext, userService);
|
||||
apiDocService = new ApiDocService(mapper, servletContext, userService);
|
||||
}
|
||||
|
||||
private void setApiDocumentation(Map<String, ApiEndpoint> docs) throws Exception {
|
||||
|
||||
@@ -20,6 +20,8 @@ import org.mockito.MockedStatic;
|
||||
import stirling.software.SPDF.model.SignatureFile;
|
||||
import stirling.software.common.configuration.InstallationPathConfig;
|
||||
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
class SignatureServiceTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@@ -53,7 +55,7 @@ class SignatureServiceTest {
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Initialize the service with our temp directory
|
||||
signatureService = new SharedSignatureService();
|
||||
signatureService = new SharedSignatureService(JsonMapper.builder().build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user