mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-04-06 03:19:39 +02:00
Security fixes, enterprise stuff and more (#3241)
# Description of Changes Please provide a summary of the changes, including: - Enable user to add custom JAVA ops with env JAVA_CUSTOM_OPTS - Added support for prometheus (enabled via JAVA_CUSTOM_OPTS + enterprise license) - Changed settings from enterprise naming to 'Premium' - KeygenLicense Check to support offline licenses - Disable URL-to-PDF due to huge security bug - Remove loud Split PDF logs - addUsers renamed to adminSettings - Added Usage analytics page - Add user button to only be enabled based on total users free - Improve Merge memory usage Closes #(issue_number) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: a <a> Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com> Co-authored-by: Connor Yoh <con.yoh13@gmail.com>
This commit is contained in:
@@ -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 = Files.createTempFile("merged-", ".pdf").toFile();
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ public class SplitPdfBySizeController {
|
||||
@Autowired
|
||||
public SplitPdfBySizeController(CustomPDFDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
log.info(
|
||||
"SplitPdfBySizeController initialized with pdfDocumentFactory: {}",
|
||||
pdfDocumentFactory.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
||||
@@ -57,53 +54,49 @@ public class SplitPdfBySizeController {
|
||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request)
|
||||
throws Exception {
|
||||
|
||||
log.info("Starting PDF split process with request: {}", request);
|
||||
log.debug("Starting PDF split process with request: {}", request);
|
||||
MultipartFile file = request.getFileInput();
|
||||
log.info(
|
||||
"File received: name={}, size={} bytes",
|
||||
file.getOriginalFilename(),
|
||||
file.getSize());
|
||||
|
||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
log.info("Created temporary zip file: {}", zipFile);
|
||||
log.debug("Created temporary zip file: {}", zipFile);
|
||||
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
log.info("Base filename for output: {}", filename);
|
||||
log.debug("Base filename for output: {}", filename);
|
||||
|
||||
byte[] data = null;
|
||||
try {
|
||||
log.info("Reading input file bytes");
|
||||
log.debug("Reading input file bytes");
|
||||
byte[] pdfBytes = file.getBytes();
|
||||
log.info("Successfully read {} bytes from input file", pdfBytes.length);
|
||||
log.debug("Successfully read {} bytes from input file", pdfBytes.length);
|
||||
|
||||
log.info("Creating ZIP output stream");
|
||||
log.debug("Creating ZIP output stream");
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
log.info("Loading PDF document");
|
||||
log.debug("Loading PDF document");
|
||||
try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Successfully loaded PDF with {} pages",
|
||||
sourceDocument.getNumberOfPages());
|
||||
|
||||
int type = request.getSplitType();
|
||||
String value = request.getSplitValue();
|
||||
log.info("Split type: {}, Split value: {}", type, value);
|
||||
log.debug("Split type: {}, Split value: {}", type, value);
|
||||
|
||||
if (type == 0) {
|
||||
log.info("Processing split by size");
|
||||
log.debug("Processing split by size");
|
||||
long maxBytes = GeneralUtils.convertSizeToBytes(value);
|
||||
log.info("Max bytes per document: {}", maxBytes);
|
||||
log.debug("Max bytes per document: {}", maxBytes);
|
||||
handleSplitBySize(sourceDocument, maxBytes, zipOut, filename);
|
||||
} else if (type == 1) {
|
||||
log.info("Processing split by page count");
|
||||
log.debug("Processing split by page count");
|
||||
int pageCount = Integer.parseInt(value);
|
||||
log.info("Pages per document: {}", pageCount);
|
||||
log.debug("Pages per document: {}", pageCount);
|
||||
handleSplitByPageCount(sourceDocument, pageCount, zipOut, filename);
|
||||
} else if (type == 2) {
|
||||
log.info("Processing split by document count");
|
||||
log.debug("Processing split by document count");
|
||||
int documentCount = Integer.parseInt(value);
|
||||
log.info("Total number of documents: {}", documentCount);
|
||||
log.debug("Total number of documents: {}", documentCount);
|
||||
handleSplitByDocCount(sourceDocument, documentCount, zipOut, filename);
|
||||
} else {
|
||||
log.error("Invalid split type: {}", type);
|
||||
@@ -111,7 +104,7 @@ public class SplitPdfBySizeController {
|
||||
"Invalid argument for split type: " + type);
|
||||
}
|
||||
|
||||
log.info("PDF splitting completed successfully");
|
||||
log.debug("PDF splitting completed successfully");
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading or processing PDF document", e);
|
||||
throw e;
|
||||
@@ -126,23 +119,23 @@ public class SplitPdfBySizeController {
|
||||
throw e; // Re-throw to ensure proper error response
|
||||
} finally {
|
||||
try {
|
||||
log.info("Reading ZIP file data");
|
||||
log.debug("Reading ZIP file data");
|
||||
data = Files.readAllBytes(zipFile);
|
||||
log.info("Successfully read {} bytes from ZIP file", data.length);
|
||||
log.debug("Successfully read {} bytes from ZIP file", data.length);
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading ZIP file data", e);
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Deleting temporary ZIP file");
|
||||
log.debug("Deleting temporary ZIP file");
|
||||
boolean deleted = Files.deleteIfExists(zipFile);
|
||||
log.info("Temporary ZIP file deleted: {}", deleted);
|
||||
log.debug("Temporary ZIP file deleted: {}", deleted);
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting temporary ZIP file", e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Returning response with {} bytes of data", data != null ? data.length : 0);
|
||||
log.debug("Returning response with {} bytes of data", data != null ? data.length : 0);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
@@ -150,7 +143,7 @@ public class SplitPdfBySizeController {
|
||||
private void handleSplitBySize(
|
||||
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
|
||||
throws IOException {
|
||||
log.info("Starting handleSplitBySize with maxBytes={}", maxBytes);
|
||||
log.debug("Starting handleSplitBySize with maxBytes={}", maxBytes);
|
||||
|
||||
PDDocument currentDoc =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
@@ -163,7 +156,7 @@ public class SplitPdfBySizeController {
|
||||
|
||||
for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) {
|
||||
PDPage page = sourceDocument.getPage(pageIndex);
|
||||
log.info("Processing page {} of {}", pageIndex + 1, totalPages);
|
||||
log.debug("Processing page {} of {}", pageIndex + 1, totalPages);
|
||||
|
||||
// Add the page to current document
|
||||
PDPage newPage = new PDPage(page.getCOSObject());
|
||||
@@ -177,21 +170,21 @@ public class SplitPdfBySizeController {
|
||||
|| (pageAdded >= 20); // Always check after 20 pages
|
||||
|
||||
if (shouldCheckSize) {
|
||||
log.info("Performing size check after {} pages", pageAdded);
|
||||
log.debug("Performing size check after {} pages", pageAdded);
|
||||
ByteArrayOutputStream checkSizeStream = new ByteArrayOutputStream();
|
||||
currentDoc.save(checkSizeStream);
|
||||
long actualSize = checkSizeStream.size();
|
||||
log.info("Current document size: {} bytes (max: {} bytes)", actualSize, maxBytes);
|
||||
log.debug("Current document size: {} bytes (max: {} bytes)", actualSize, maxBytes);
|
||||
|
||||
if (actualSize > maxBytes) {
|
||||
// We exceeded the limit - remove the last page and save
|
||||
if (currentDoc.getNumberOfPages() > 1) {
|
||||
currentDoc.removePage(currentDoc.getNumberOfPages() - 1);
|
||||
pageIndex--; // Process this page again in the next document
|
||||
log.info("Size limit exceeded - removed last page");
|
||||
log.debug("Size limit exceeded - removed last page");
|
||||
}
|
||||
|
||||
log.info(
|
||||
log.debug(
|
||||
"Saving document with {} pages as part {}",
|
||||
currentDoc.getNumberOfPages(),
|
||||
fileIndex);
|
||||
@@ -206,7 +199,7 @@ public class SplitPdfBySizeController {
|
||||
int pagesToLookAhead = Math.min(5, totalPages - pageIndex - 1);
|
||||
|
||||
if (pagesToLookAhead > 0) {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Testing {} upcoming pages for potential addition",
|
||||
pagesToLookAhead);
|
||||
|
||||
@@ -231,12 +224,12 @@ public class SplitPdfBySizeController {
|
||||
|
||||
if (testSize <= maxBytes) {
|
||||
extraPagesAdded++;
|
||||
log.info(
|
||||
log.debug(
|
||||
"Test: Can add page {} (size would be {})",
|
||||
testPageIndex + 1,
|
||||
testSize);
|
||||
} else {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Test: Cannot add page {} (size would be {})",
|
||||
testPageIndex + 1,
|
||||
testSize);
|
||||
@@ -248,7 +241,7 @@ public class SplitPdfBySizeController {
|
||||
|
||||
// Add the pages we verified would fit
|
||||
if (extraPagesAdded > 0) {
|
||||
log.info("Adding {} verified pages ahead", extraPagesAdded);
|
||||
log.debug("Adding {} verified pages ahead", extraPagesAdded);
|
||||
for (int i = 0; i < extraPagesAdded; i++) {
|
||||
int extraPageIndex = pageIndex + 1 + i;
|
||||
PDPage extraPage = sourceDocument.getPage(extraPageIndex);
|
||||
@@ -265,26 +258,26 @@ public class SplitPdfBySizeController {
|
||||
|
||||
// Save final document if it has any pages
|
||||
if (currentDoc.getNumberOfPages() > 0) {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Saving final document with {} pages as part {}",
|
||||
currentDoc.getNumberOfPages(),
|
||||
fileIndex);
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
}
|
||||
|
||||
log.info("Completed handleSplitBySize with {} document parts created", fileIndex - 1);
|
||||
log.debug("Completed handleSplitBySize with {} document parts created", fileIndex - 1);
|
||||
}
|
||||
|
||||
private void handleSplitByPageCount(
|
||||
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
|
||||
throws IOException {
|
||||
log.info("Starting handleSplitByPageCount with pageCount={}", pageCount);
|
||||
log.debug("Starting handleSplitByPageCount with pageCount={}", pageCount);
|
||||
int currentPageCount = 0;
|
||||
log.info("Creating initial output document");
|
||||
log.debug("Creating initial output document");
|
||||
PDDocument currentDoc = null;
|
||||
try {
|
||||
currentDoc = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
log.info("Successfully created initial output document");
|
||||
log.debug("Successfully created initial output document");
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating initial output document", e);
|
||||
throw new IOException("Failed to create initial output document", e);
|
||||
@@ -293,49 +286,49 @@ public class SplitPdfBySizeController {
|
||||
int fileIndex = 1;
|
||||
int pageIndex = 0;
|
||||
int totalPages = sourceDocument.getNumberOfPages();
|
||||
log.info("Processing {} pages", totalPages);
|
||||
log.debug("Processing {} pages", totalPages);
|
||||
|
||||
try {
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
pageIndex++;
|
||||
log.info("Processing page {} of {}", pageIndex, totalPages);
|
||||
log.debug("Processing page {} of {}", pageIndex, totalPages);
|
||||
|
||||
try {
|
||||
log.info("Adding page {} to current document", pageIndex);
|
||||
log.debug("Adding page {} to current document", pageIndex);
|
||||
currentDoc.addPage(page);
|
||||
log.info("Successfully added page {} to current document", pageIndex);
|
||||
log.debug("Successfully added page {} to current document", pageIndex);
|
||||
} catch (Exception e) {
|
||||
log.error("Error adding page {} to current document", pageIndex, e);
|
||||
throw new IOException("Failed to add page to document", e);
|
||||
}
|
||||
|
||||
currentPageCount++;
|
||||
log.info("Current page count: {}/{}", currentPageCount, pageCount);
|
||||
log.debug("Current page count: {}/{}", currentPageCount, pageCount);
|
||||
|
||||
if (currentPageCount == pageCount) {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Reached target page count ({}), saving current document as part {}",
|
||||
pageCount,
|
||||
fileIndex);
|
||||
try {
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
log.info("Successfully saved document part {}", fileIndex - 1);
|
||||
log.debug("Successfully saved document part {}", fileIndex - 1);
|
||||
} catch (Exception e) {
|
||||
log.error("Error saving document part {}", fileIndex - 1, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Creating new document for next part");
|
||||
log.debug("Creating new document for next part");
|
||||
currentDoc = new PDDocument();
|
||||
log.info("Successfully created new document");
|
||||
log.debug("Successfully created new document");
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating new document for next part", e);
|
||||
throw new IOException("Failed to create new document", e);
|
||||
}
|
||||
|
||||
currentPageCount = 0;
|
||||
log.info("Reset current page count to 0");
|
||||
log.debug("Reset current page count to 0");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -346,34 +339,34 @@ public class SplitPdfBySizeController {
|
||||
// Add the last document if it contains any pages
|
||||
try {
|
||||
if (currentDoc.getPages().getCount() != 0) {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Saving final document with {} pages as part {}",
|
||||
currentDoc.getPages().getCount(),
|
||||
fileIndex);
|
||||
try {
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
log.info("Successfully saved final document part {}", fileIndex - 1);
|
||||
log.debug("Successfully saved final document part {}", fileIndex - 1);
|
||||
} catch (Exception e) {
|
||||
log.error("Error saving final document part {}", fileIndex - 1, e);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
log.info("Final document has no pages, skipping");
|
||||
log.debug("Final document has no pages, skipping");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error checking or saving final document", e);
|
||||
throw new IOException("Failed to process final document", e);
|
||||
} finally {
|
||||
try {
|
||||
log.info("Closing final document");
|
||||
log.debug("Closing final document");
|
||||
currentDoc.close();
|
||||
log.info("Successfully closed final document");
|
||||
log.debug("Successfully closed final document");
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing final document", e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Completed handleSplitByPageCount with {} document parts created", fileIndex - 1);
|
||||
log.debug("Completed handleSplitByPageCount with {} document parts created", fileIndex - 1);
|
||||
}
|
||||
|
||||
private void handleSplitByDocCount(
|
||||
@@ -382,40 +375,40 @@ public class SplitPdfBySizeController {
|
||||
ZipOutputStream zipOut,
|
||||
String baseFilename)
|
||||
throws IOException {
|
||||
log.info("Starting handleSplitByDocCount with documentCount={}", documentCount);
|
||||
log.debug("Starting handleSplitByDocCount with documentCount={}", documentCount);
|
||||
int totalPageCount = sourceDocument.getNumberOfPages();
|
||||
log.info("Total pages in source document: {}", totalPageCount);
|
||||
log.debug("Total pages in source document: {}", totalPageCount);
|
||||
|
||||
int pagesPerDocument = totalPageCount / documentCount;
|
||||
int extraPages = totalPageCount % documentCount;
|
||||
log.info("Pages per document: {}, Extra pages: {}", pagesPerDocument, extraPages);
|
||||
log.debug("Pages per document: {}, Extra pages: {}", pagesPerDocument, extraPages);
|
||||
|
||||
int currentPageIndex = 0;
|
||||
int fileIndex = 1;
|
||||
|
||||
for (int i = 0; i < documentCount; i++) {
|
||||
log.info("Creating document {} of {}", i + 1, documentCount);
|
||||
log.debug("Creating document {} of {}", i + 1, documentCount);
|
||||
PDDocument currentDoc = null;
|
||||
try {
|
||||
currentDoc = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
log.info("Successfully created document {} of {}", i + 1, documentCount);
|
||||
log.debug("Successfully created document {} of {}", i + 1, documentCount);
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating document {} of {}", i + 1, documentCount, e);
|
||||
throw new IOException("Failed to create document", e);
|
||||
}
|
||||
|
||||
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
||||
log.info("Adding {} pages to document {}", pagesToAdd, i + 1);
|
||||
log.debug("Adding {} pages to document {}", pagesToAdd, i + 1);
|
||||
|
||||
for (int j = 0; j < pagesToAdd; j++) {
|
||||
try {
|
||||
log.info(
|
||||
log.debug(
|
||||
"Adding page {} (index {}) to document {}",
|
||||
j + 1,
|
||||
currentPageIndex,
|
||||
i + 1);
|
||||
currentDoc.addPage(sourceDocument.getPage(currentPageIndex));
|
||||
log.info("Successfully added page {} to document {}", j + 1, i + 1);
|
||||
log.debug("Successfully added page {} to document {}", j + 1, i + 1);
|
||||
currentPageIndex++;
|
||||
} catch (Exception e) {
|
||||
log.error("Error adding page {} to document {}", j + 1, i + 1, e);
|
||||
@@ -424,37 +417,37 @@ public class SplitPdfBySizeController {
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Saving document {} with {} pages", i + 1, pagesToAdd);
|
||||
log.debug("Saving document {} with {} pages", i + 1, pagesToAdd);
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
log.info("Successfully saved document {}", i + 1);
|
||||
log.debug("Successfully saved document {}", i + 1);
|
||||
} catch (Exception e) {
|
||||
log.error("Error saving document {}", i + 1, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Completed handleSplitByDocCount with {} documents created", documentCount);
|
||||
log.debug("Completed handleSplitByDocCount with {} documents created", documentCount);
|
||||
}
|
||||
|
||||
private void saveDocumentToZip(
|
||||
PDDocument document, ZipOutputStream zipOut, String baseFilename, int index)
|
||||
throws IOException {
|
||||
log.info("Starting saveDocumentToZip for document part {}", index);
|
||||
log.debug("Starting saveDocumentToZip for document part {}", index);
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
log.info("Saving document part {} to byte array", index);
|
||||
log.debug("Saving document part {} to byte array", index);
|
||||
document.save(outStream);
|
||||
log.info("Successfully saved document part {} ({} bytes)", index, outStream.size());
|
||||
log.debug("Successfully saved document part {} ({} bytes)", index, outStream.size());
|
||||
} catch (Exception e) {
|
||||
log.error("Error saving document part {} to byte array", index, e);
|
||||
throw new IOException("Failed to save document to byte array", e);
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Closing document part {}", index);
|
||||
log.debug("Closing document part {}", index);
|
||||
document.close();
|
||||
log.info("Successfully closed document part {}", index);
|
||||
log.debug("Successfully closed document part {}", index);
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing document part {}", index, e);
|
||||
// Continue despite close error
|
||||
@@ -463,17 +456,17 @@ public class SplitPdfBySizeController {
|
||||
try {
|
||||
// Create a new zip entry
|
||||
String entryName = baseFilename + "_" + index + ".pdf";
|
||||
log.info("Creating ZIP entry: {}", entryName);
|
||||
log.debug("Creating ZIP entry: {}", entryName);
|
||||
ZipEntry zipEntry = new ZipEntry(entryName);
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
|
||||
byte[] bytes = outStream.toByteArray();
|
||||
log.info("Writing {} bytes to ZIP entry", bytes.length);
|
||||
log.debug("Writing {} bytes to ZIP entry", bytes.length);
|
||||
zipOut.write(bytes);
|
||||
|
||||
log.info("Closing ZIP entry");
|
||||
log.debug("Closing ZIP entry");
|
||||
zipOut.closeEntry();
|
||||
log.info("Successfully added document part {} to ZIP", index);
|
||||
log.debug("Successfully added document part {} to ZIP", index);
|
||||
} catch (Exception e) {
|
||||
log.error("Error adding document part {} to ZIP", index, e);
|
||||
throw new IOException("Failed to add document to ZIP file", e);
|
||||
|
||||
@@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
@@ -47,10 +48,15 @@ public class UserController {
|
||||
private static final String LOGIN_MESSAGETYPE_CREDSUPDATED = "/login?messageType=credsUpdated";
|
||||
private final UserService userService;
|
||||
private final SessionPersistentRegistry sessionRegistry;
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
public UserController(UserService userService, SessionPersistentRegistry sessionRegistry) {
|
||||
public UserController(
|
||||
UserService userService,
|
||||
SessionPersistentRegistry sessionRegistry,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.userService = userService;
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@@ -194,39 +200,44 @@ public class UserController {
|
||||
boolean forceChange)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
if (!userService.isUsernameValid(username)) {
|
||||
return new RedirectView("/addUsers?messageType=invalidUsername", true);
|
||||
return new RedirectView("/adminSettings?messageType=invalidUsername", true);
|
||||
}
|
||||
if (applicationProperties.getPremium().isEnabled()
|
||||
&& applicationProperties.getPremium().getMaxUsers()
|
||||
<= userService.getTotalUsersCount()) {
|
||||
return new RedirectView("/adminSettings?messageType=maxUsersReached", true);
|
||||
}
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
if (user.getUsername().equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=usernameExists", true);
|
||||
return new RedirectView("/adminSettings?messageType=usernameExists", true);
|
||||
}
|
||||
}
|
||||
if (userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=usernameExists", true);
|
||||
return new RedirectView("/adminSettings?messageType=usernameExists", true);
|
||||
}
|
||||
try {
|
||||
// Validate the role
|
||||
Role roleEnum = Role.fromString(role);
|
||||
if (roleEnum == Role.INTERNAL_API_USER) {
|
||||
// If the role is INTERNAL_API_USER, reject the request
|
||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||
return new RedirectView("/adminSettings?messageType=invalidRole", true);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// If the role ID is not valid, redirect with an error message
|
||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||
return new RedirectView("/adminSettings?messageType=invalidRole", true);
|
||||
}
|
||||
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
|
||||
userService.saveUser(username, AuthenticationType.SSO, role);
|
||||
} else {
|
||||
if (password.isBlank()) {
|
||||
return new RedirectView("/addUsers?messageType=invalidPassword", true);
|
||||
return new RedirectView("/adminSettings?messageType=invalidPassword", true);
|
||||
}
|
||||
userService.saveUser(username, password, role, forceChange);
|
||||
}
|
||||
return new RedirectView(
|
||||
"/addUsers", // Redirect to account page after adding the user
|
||||
"/adminSettings", // Redirect to account page after adding the user
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -239,32 +250,32 @@ public class UserController {
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
if (!userOpt.isPresent()) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||
return new RedirectView("/adminSettings?messageType=userNotFound", true);
|
||||
}
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||
return new RedirectView("/adminSettings?messageType=userNotFound", true);
|
||||
}
|
||||
// Get the currently authenticated username
|
||||
String currentUsername = authentication.getName();
|
||||
// Check if the provided username matches the current session's username
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=downgradeCurrentUser", true);
|
||||
return new RedirectView("/adminSettings?messageType=downgradeCurrentUser", true);
|
||||
}
|
||||
try {
|
||||
// Validate the role
|
||||
Role roleEnum = Role.fromString(role);
|
||||
if (roleEnum == Role.INTERNAL_API_USER) {
|
||||
// If the role is INTERNAL_API_USER, reject the request
|
||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||
return new RedirectView("/adminSettings?messageType=invalidRole", true);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// If the role ID is not valid, redirect with an error message
|
||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||
return new RedirectView("/adminSettings?messageType=invalidRole", true);
|
||||
}
|
||||
User user = userOpt.get();
|
||||
userService.changeRole(user, role);
|
||||
return new RedirectView(
|
||||
"/addUsers", // Redirect to account page after adding the user
|
||||
"/adminSettings", // Redirect to account page after adding the user
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -277,16 +288,16 @@ public class UserController {
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
if (userOpt.isEmpty()) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||
return new RedirectView("/adminSettings?messageType=userNotFound", true);
|
||||
}
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||
return new RedirectView("/adminSettings?messageType=userNotFound", true);
|
||||
}
|
||||
// Get the currently authenticated username
|
||||
String currentUsername = authentication.getName();
|
||||
// Check if the provided username matches the current session's username
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=disabledCurrentUser", true);
|
||||
return new RedirectView("/adminSettings?messageType=disabledCurrentUser", true);
|
||||
}
|
||||
User user = userOpt.get();
|
||||
userService.changeUserEnabled(user, enabled);
|
||||
@@ -314,7 +325,7 @@ public class UserController {
|
||||
}
|
||||
}
|
||||
return new RedirectView(
|
||||
"/addUsers", // Redirect to account page after adding the user
|
||||
"/adminSettings", // Redirect to account page after adding the user
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -323,13 +334,13 @@ public class UserController {
|
||||
public RedirectView deleteUser(
|
||||
@PathVariable("username") String username, Authentication authentication) {
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=deleteUsernameExists", true);
|
||||
return new RedirectView("/adminSettings?messageType=deleteUsernameExists", true);
|
||||
}
|
||||
// Get the currently authenticated username
|
||||
String currentUsername = authentication.getName();
|
||||
// Check if the provided username matches the current session's username
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=deleteCurrentUser", true);
|
||||
return new RedirectView("/adminSettings?messageType=deleteCurrentUser", true);
|
||||
}
|
||||
// Invalidate all sessions before deleting the user
|
||||
List<SessionInformation> sessionsInformations =
|
||||
@@ -339,7 +350,7 @@ public class UserController {
|
||||
sessionRegistry.removeSessionInformation(sessionsInformation.getSessionId());
|
||||
}
|
||||
userService.deleteUser(username);
|
||||
return new RedirectView("/addUsers", true);
|
||||
return new RedirectView("/adminSettings", true);
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.config.RuntimePathConfig;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
@@ -35,12 +36,16 @@ public class ConvertWebsiteToPDF {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired
|
||||
public ConvertWebsiteToPDF(
|
||||
CustomPDFDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
|
||||
CustomPDFDocumentFactory pdfDocumentFactory,
|
||||
RuntimePathConfig runtimePathConfig,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.runtimePathConfig = runtimePathConfig;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||
@@ -53,6 +58,9 @@ 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.");
|
||||
}
|
||||
// Validate the URL format
|
||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
||||
throw new IllegalArgumentException("Invalid URL format provided.");
|
||||
|
||||
@@ -118,11 +118,11 @@ public class AccountWebController {
|
||||
|
||||
if (securityProps.isSaml2Active()
|
||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()
|
||||
&& applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||
&& applicationProperties.getPremium().isEnabled()) {
|
||||
String samlIdp = saml2.getProvider();
|
||||
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
|
||||
|
||||
if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) {
|
||||
if (applicationProperties.getPremium().getProFeatures().isSsoAutoLogin()) {
|
||||
return "redirect:" + request.getRequestURL() + saml2AuthenticationPath;
|
||||
} else {
|
||||
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
|
||||
@@ -195,7 +195,13 @@ public class AccountWebController {
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/addUsers")
|
||||
@GetMapping("/usage")
|
||||
public String showUsage() {
|
||||
return "usage";
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/adminSettings")
|
||||
public String showAddUserForm(
|
||||
HttpServletRequest request, Model model, Authentication authentication) {
|
||||
List<User> allUsers = userRepository.findAll();
|
||||
@@ -318,7 +324,9 @@ public class AccountWebController {
|
||||
model.addAttribute("totalUsers", allUsers.size());
|
||||
model.addAttribute("activeUsers", activeUsers);
|
||||
model.addAttribute("disabledUsers", disabledUsers);
|
||||
return "addUsers";
|
||||
|
||||
model.addAttribute("maxEnterpriseUsers", applicationProperties.getPremium().getMaxUsers());
|
||||
return "adminSettings";
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
|
||||
@@ -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,25 +211,43 @@ public class MetricsController {
|
||||
}
|
||||
|
||||
private double getRequestCount(String method, Optional<String> endpoint) {
|
||||
log.info(
|
||||
"Getting request count for method: {}, endpoint: {}",
|
||||
method,
|
||||
endpoint.orElse("all"));
|
||||
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();
|
||||
log.info("Request count: {}", count);
|
||||
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) {
|
||||
log.info("Getting endpoint counts for method: {}", method);
|
||||
Map<String, Double> counts = new HashMap<>();
|
||||
meterRegistry
|
||||
.find("http.requests")
|
||||
@@ -235,28 +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());
|
||||
log.info("Found {} endpoints with counts", result.size());
|
||||
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) {
|
||||
log.info(
|
||||
"Getting unique user count for method: {}, endpoint: {}",
|
||||
method,
|
||||
endpoint.orElse("all"));
|
||||
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");
|
||||
@@ -264,12 +329,10 @@ public class MetricsController {
|
||||
uniqueUsers.add(session);
|
||||
}
|
||||
});
|
||||
log.info("Unique user count: {}", uniqueUsers.size());
|
||||
return uniqueUsers.size();
|
||||
}
|
||||
|
||||
private List<EndpointCount> getUniqueUserCounts(String method) {
|
||||
log.info("Getting unique user counts for method: {}", method);
|
||||
Map<String, Set<String>> uniqueUsers = new HashMap<>();
|
||||
meterRegistry
|
||||
.find("http.requests")
|
||||
@@ -283,13 +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());
|
||||
log.info("Found {} endpoints with unique user counts", result.size());
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user