Fix PATH order for python3 calls, improve merge memory

This commit is contained in:
Anthony Stirling 2025-03-25 17:36:01 +00:00
parent 905fb67766
commit dfd567b803
11 changed files with 142 additions and 67 deletions

View File

@ -34,7 +34,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
UNO_PATH=/usr/lib/libreoffice/program \
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
PATH=/opt/venv/bin:$PATH
PATH=$PATH:/opt/venv/bin
# JDK for app

View File

@ -43,7 +43,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
UNO_PATH=/usr/lib/libreoffice/program \
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
PATH=/opt/venv/bin:$PATH
PATH=$PATH:/opt/venv/bin
# JDK for app

View File

@ -28,13 +28,13 @@ public class KeygenLicenseVerifier {
// License verification configuration
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
private static final String PUBLIC_KEY =
"9fbc0d78593dcfcf03c945146edd60083bf5fae77dbc08aaa3935f03ce94a58d";
private static final String CERT_PREFIX = "-----BEGIN LICENSE FILE-----";
private static final String CERT_SUFFIX = "-----END LICENSE FILE-----";
private static final String JWT_PREFIX = "key/";
private static final ObjectMapper objectMapper = new ObjectMapper();
@ -77,8 +77,7 @@ public class KeygenLicenseVerifier {
encodedPayload = encodedPayload.replace(CERT_SUFFIX, "");
// Remove all newlines
encodedPayload = encodedPayload.replaceAll("\\r?\\n", "");
byte[] payloadBytes = Base64.getDecoder().decode(encodedPayload);
String payload = new String(payloadBytes);
@ -403,7 +402,7 @@ public class KeygenLicenseVerifier {
return false;
}
}
private boolean verifyStandardLicense(String licenseKey) {
try {
log.info("Checking standard license key");

View File

@ -289,9 +289,9 @@ public class EndpointConfiguration {
if (!runningEE) {
disableGroup("enterprise");
}
if(!applicationProperties.getSystem().getEnableUrlToPDF()) {
disableEndpoint("url-to-pdf");
if (!applicationProperties.getSystem().getEnableUrlToPDF()) {
disableEndpoint("url-to-pdf");
}
}

View File

@ -125,8 +125,7 @@ public class MergeController {
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
throws IOException {
List<File> filesToDelete = new ArrayList<>(); // List of temporary files to delete
ByteArrayOutputStream docOutputstream =
new ByteArrayOutputStream(); // Stream for the merged document
File mergedTempFile = null;
PDDocument mergedDocument = null;
boolean removeCertSign = form.isRemoveCertSign();
@ -139,21 +138,24 @@ public class MergeController {
form.getSortType())); // Sort files based on the given sort type
PDFMergerUtility mergerUtility = new PDFMergerUtility();
long totalSize = 0;
for (MultipartFile multipartFile : files) {
totalSize += multipartFile.getSize();
File tempFile =
GeneralUtils.convertMultipartFileToFile(
multipartFile); // Convert MultipartFile to File
filesToDelete.add(tempFile); // Add temp file to the list for later deletion
mergerUtility.addSource(tempFile); // Add source file to the merger utility
}
mergerUtility.setDestinationStream(
docOutputstream); // Set the output stream for the merged document
mergerUtility.mergeDocuments(null); // Merge the documents
byte[] mergedPdfBytes = docOutputstream.toByteArray(); // Get merged document bytes
mergedTempFile = File.createTempFile("merged-", ".pdf");
mergerUtility.setDestinationFileName(mergedTempFile.getAbsolutePath());
mergerUtility.mergeDocuments(
pdfDocumentFactory.getStreamCacheFunction(totalSize)); // Merge the documents
// Load the merged PDF document
mergedDocument = pdfDocumentFactory.load(mergedPdfBytes);
mergedDocument = pdfDocumentFactory.load(mergedTempFile);
// Remove signatures if removeCertSign is true
if (removeCertSign) {
@ -180,21 +182,23 @@ public class MergeController {
String mergedFileName =
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_merged_unsigned.pdf";
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), mergedFileName); // Return the modified PDF
return WebResponseUtils.boasToWebResponse(
baos, mergedFileName); // Return the modified PDF
} catch (Exception ex) {
log.error("Error in merge pdf process", ex);
throw ex;
} finally {
if (mergedDocument != null) {
mergedDocument.close(); // Close the merged document
}
for (File file : filesToDelete) {
if (file != null) {
Files.deleteIfExists(file.toPath()); // Delete temporary files
}
}
docOutputstream.close();
if (mergedDocument != null) {
mergedDocument.close(); // Close the merged document
}
if (mergedTempFile != null) {
Files.deleteIfExists(mergedTempFile.toPath());
}
}
}

View File

@ -37,9 +37,12 @@ public class ConvertWebsiteToPDF {
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final RuntimePathConfig runtimePathConfig;
private final ApplicationProperties applicationProperties;
@Autowired
public ConvertWebsiteToPDF(
CustomPDFDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig, ApplicationProperties applicationProperties) {
CustomPDFDocumentFactory pdfDocumentFactory,
RuntimePathConfig runtimePathConfig,
ApplicationProperties applicationProperties) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.runtimePathConfig = runtimePathConfig;
this.applicationProperties = applicationProperties;
@ -55,9 +58,8 @@ public class ConvertWebsiteToPDF {
throws IOException, InterruptedException {
String URL = request.getUrlInput();
if(!applicationProperties.getSystem().getEnableUrlToPDF()) {
throw new IllegalArgumentException("This endpoint has been disabled by the admin.");
if (!applicationProperties.getSystem().getEnableUrlToPDF()) {
throw new IllegalArgumentException("This endpoint has been disabled by the admin.");
}
// Validate the URL format
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {

View File

@ -215,4 +215,4 @@ public class OCRController {
log.error("Error walking directory {}: {}", directory, e.getMessage());
}
}
}
}

View File

@ -22,6 +22,7 @@ import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointInspector;
import stirling.software.SPDF.config.StartupApplicationListener;
import stirling.software.SPDF.model.ApplicationProperties;
@ -32,15 +33,17 @@ import stirling.software.SPDF.model.ApplicationProperties;
public class MetricsController {
private final ApplicationProperties applicationProperties;
private final MeterRegistry meterRegistry;
private final EndpointInspector endpointInspector;
private boolean metricsEnabled;
public MetricsController(
ApplicationProperties applicationProperties, MeterRegistry meterRegistry) {
ApplicationProperties applicationProperties,
MeterRegistry meterRegistry,
EndpointInspector endpointInspector) {
this.applicationProperties = applicationProperties;
this.meterRegistry = meterRegistry;
this.endpointInspector = endpointInspector;
}
@PostConstruct
@ -208,16 +211,40 @@ public class MetricsController {
}
private double getRequestCount(String method, Optional<String> endpoint) {
double count =
meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter ->
!endpoint.isPresent()
|| endpoint.get()
.equals(counter.getId().getTag("uri")))
.mapToDouble(Counter::count)
.sum();
return count;
return meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter -> {
String uri = counter.getId().getTag("uri");
// Apply filtering logic - Skip if uri is null
if (uri == null) {
return false;
}
// For POST requests, only include if they start with /api/v1
if ("POST".equals(method) && !uri.contains("api/v1")) {
return false;
}
if (uri.contains(".txt")) {
return false;
}
// For GET requests, validate if we have a list of valid endpoints
final boolean validateGetEndpoints =
endpointInspector.getValidGetEndpoints().size() != 0;
if ("GET".equals(method)
&& validateGetEndpoints
&& !endpointInspector.isValidGetEndpoint(uri)) {
log.debug("Skipping invalid GET endpoint: {}", uri);
return false;
}
// Filter for specific endpoint if provided
return !endpoint.isPresent() || endpoint.get().equals(uri);
})
.mapToDouble(Counter::count)
.sum();
}
private List<EndpointCount> getEndpointCounts(String method) {
@ -229,23 +256,72 @@ public class MetricsController {
.forEach(
counter -> {
String uri = counter.getId().getTag("uri");
// Skip if uri is null
if (uri == null) {
return;
}
// For POST requests, only include if they start with /api/v1
if ("POST".equals(method) && !uri.contains("api/v1")) {
return;
}
if (uri.contains(".txt")) {
return;
}
// For GET requests, validate if we have a list of valid endpoints
final boolean validateGetEndpoints =
endpointInspector.getValidGetEndpoints().size() != 0;
if ("GET".equals(method)
&& validateGetEndpoints
&& !endpointInspector.isValidGetEndpoint(uri)) {
log.debug("Skipping invalid GET endpoint: {}", uri);
return;
}
counts.merge(uri, counter.count(), Double::sum);
});
List<EndpointCount> result =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return result;
return counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
}
private double getUniqueUserCount(String method, Optional<String> endpoint) {
Set<String> uniqueUsers = new HashSet<>();
meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter ->
!endpoint.isPresent()
|| endpoint.get().equals(counter.getId().getTag("uri")))
counter -> {
String uri = counter.getId().getTag("uri");
// Skip if uri is null
if (uri == null) {
return false;
}
// For POST requests, only include if they start with /api/v1
if ("POST".equals(method) && !uri.contains("api/v1")) {
return false;
}
if (uri.contains(".txt")) {
return false;
}
// For GET requests, validate if we have a list of valid endpoints
final boolean validateGetEndpoints =
endpointInspector.getValidGetEndpoints().size() != 0;
if ("GET".equals(method)
&& validateGetEndpoints
&& !endpointInspector.isValidGetEndpoint(uri)) {
log.debug("Skipping invalid GET endpoint: {}", uri);
return false;
}
return !endpoint.isPresent() || endpoint.get().equals(uri);
})
.forEach(
counter -> {
String session = counter.getId().getTag("session");
@ -270,12 +346,10 @@ public class MetricsController {
uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session);
}
});
List<EndpointCount> result =
uniqueUsers.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return result;
return uniqueUsers.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
}
@GetMapping("/uptime")

View File

@ -139,7 +139,7 @@ public class CustomPDFDocumentFactory {
* Determine the appropriate caching strategy based on file size and available memory. This
* common method is used by both password and non-password loading paths.
*/
private StreamCacheCreateFunction getStreamCacheFunction(long contentSize) {
public StreamCacheCreateFunction getStreamCacheFunction(long contentSize) {
long maxMemory = Runtime.getRuntime().maxMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
long totalMemory = Runtime.getRuntime().totalMemory();

View File

@ -72,10 +72,6 @@ premium:
author: username
creator: Stirling-PDF
producer: Stirling-PDF
enterpriseFeatures:
persistentMetrics:
enabled: false
retentionDays: 90
legal:
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder

View File

@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.converters;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -21,7 +20,7 @@ public class ConvertWebsiteToPdfTest {
@Mock private RuntimePathConfig runtimePathConfig;
private ApplicationProperties applicationProperties;
private ConvertWebsiteToPDF convertWebsiteToPDF;
@BeforeEach
@ -29,13 +28,14 @@ public class ConvertWebsiteToPdfTest {
MockitoAnnotations.openMocks(this);
applicationProperties = new ApplicationProperties();
applicationProperties.getSystem().setEnableUrlToPDF(true);
convertWebsiteToPDF = new ConvertWebsiteToPDF(mockPdfDocumentFactory, runtimePathConfig, applicationProperties);
convertWebsiteToPDF =
new ConvertWebsiteToPDF(
mockPdfDocumentFactory, runtimePathConfig, applicationProperties);
}
@Test
public void test_exemption_is_thrown_when_invalid_url_format_provided() {
String invalid_format_Url = "invalid-url";
UrlToPdfRequest request = new UrlToPdfRequest();