From 5f771b785130154ed47952635b7acef371ffe0ec Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 30 Dec 2023 19:11:27 +0000 Subject: [PATCH] formatting --- .../software/SPDF/LibreOfficeListener.java | 38 +- .../software/SPDF/SPdfApplication.java | 28 +- .../software/SPDF/config/AppConfig.java | 38 +- .../stirling/software/SPDF/config/Beans.java | 21 +- .../SPDF/config/CleanUrlInterceptor.java | 98 +-- .../SPDF/config/ConfigInitializer.java | 198 ++--- .../SPDF/config/EndpointConfiguration.java | 60 +- .../SPDF/config/EndpointInterceptor.java | 8 +- .../software/SPDF/config/MetricsConfig.java | 3 +- .../software/SPDF/config/MetricsFilter.java | 64 +- .../software/SPDF/config/OpenApiConfig.java | 59 +- .../config/StartupApplicationListener.java | 2 - .../software/SPDF/config/WebMvcConfig.java | 7 +- .../config/YamlPropertySourceFactory.java | 10 +- .../CustomAuthenticationFailureHandler.java | 38 +- .../CustomAuthenticationSuccessHandler.java | 29 +- .../security/CustomUserDetailsService.java | 39 +- .../config/security/FirstLoginFilter.java | 25 +- .../config/security/IPRateLimitingFilter.java | 71 +- .../config/security/InitialSecuritySetup.java | 123 +-- .../config/security/LoginAttemptService.java | 42 +- .../security/RateLimitResetScheduler.java | 3 +- .../security/SecurityConfiguration.java | 156 ++-- .../security/UserAuthenticationFilter.java | 81 +- .../security/UserBasedRateLimitingFilter.java | 53 +- .../SPDF/config/security/UserService.java | 105 ++- .../SPDF/controller/api/CropController.java | 88 ++- .../SPDF/controller/api/MergeController.java | 72 +- .../api/MultiPageLayoutController.java | 160 ++-- .../controller/api/PdfOverlayController.java | 83 +- .../api/RearrangePagesPDFController.java | 344 +++++---- .../controller/api/RotationController.java | 17 +- .../controller/api/ScalePagesController.java | 126 +-- .../controller/api/SplitPDFController.java | 28 +- .../api/SplitPdfBySectionsController.java | 37 +- .../api/SplitPdfBySizeController.java | 40 +- .../api/ToSinglePageController.java | 93 +-- .../SPDF/controller/api/UserController.java | 204 ++--- .../api/converters/ConvertHtmlToPDF.java | 50 +- .../converters/ConvertImgPDFController.java | 47 +- .../api/converters/ConvertMarkdownToPdf.java | 28 +- .../converters/ConvertOfficeController.java | 41 +- .../api/converters/ConvertPDFToOffice.java | 102 ++- .../api/converters/ConvertPDFToPDFA.java | 24 +- .../api/converters/ConvertWebsiteToPDF.java | 89 +-- .../api/converters/ExtractController.java | 81 +- .../api/filters/FilterController.java | 302 ++++---- .../api/misc/AutoRenameController.java | 156 ++-- .../api/misc/AutoSplitPdfController.java | 41 +- .../api/misc/BlankPageController.java | 68 +- .../api/misc/CompressController.java | 127 +-- .../api/misc/ExtractImageScansController.java | 95 ++- .../api/misc/ExtractImagesController.java | 44 +- .../api/misc/FakeScanControllerWIP.java | 158 ++-- .../api/misc/MetadataController.java | 47 +- .../controller/api/misc/OCRController.java | 93 ++- .../api/misc/OverlayImageController.java | 11 +- .../api/misc/PageNumbersController.java | 61 +- .../controller/api/misc/RepairController.java | 21 +- .../controller/api/misc/ShowJavascript.java | 63 +- .../api/pipeline/ApiDocService.java | 85 +- .../api/pipeline/PipelineController.java | 131 ++-- .../pipeline/PipelineDirectoryProcessor.java | 162 ++-- .../api/pipeline/PipelineProcessor.java | 480 ++++++------ .../api/pipeline/UserServiceInterface.java | 1 + .../api/security/CertSignController.java | 365 +++++---- .../controller/api/security/GetInfoOnPDF.java | 384 +++++----- .../api/security/PasswordController.java | 47 +- .../api/security/RedactController.java | 58 +- .../api/security/SanitizeController.java | 154 ++-- .../api/security/WatermarkController.java | 285 ++++--- .../api/strippers/PDFTableStripper.java | 194 ++--- .../controller/web/AccountWebController.java | 211 ++--- .../web/ConverterWebController.java | 14 +- .../controller/web/GeneralWebController.java | 229 +++--- .../controller/web/HomeWebController.java | 14 +- .../controller/web/MetricsController.java | 164 ++-- .../controller/web/OtherWebController.java | 38 +- .../controller/web/SecurityWebController.java | 13 +- .../software/SPDF/model/ApiEndpoint.java | 26 +- .../SPDF/model/ApiKeyAuthenticationToken.java | 9 +- .../SPDF/model/ApplicationProperties.java | 725 +++++++++--------- .../software/SPDF/model/AttemptCounter.java | 1 + .../software/SPDF/model/Authority.java | 56 +- .../stirling/software/SPDF/model/PDFText.java | 3 +- .../software/SPDF/model/PersistentLogin.java | 49 +- .../software/SPDF/model/PipelineConfig.java | 4 +- .../SPDF/model/PipelineOperation.java | 41 +- .../stirling/software/SPDF/model/Role.java | 17 +- .../software/SPDF/model/SortTypes.java | 12 +- .../stirling/software/SPDF/model/User.java | 133 ++-- .../software/SPDF/model/api/GeneralFile.java | 5 +- .../SPDF/model/api/HandleDataRequest.java | 1 + .../software/SPDF/model/api/ImageFile.java | 3 +- .../SPDF/model/api/MultiplePDFFiles.java | 4 +- .../SPDF/model/api/PDFComparison.java | 12 +- .../SPDF/model/api/PDFComparisonAndCount.java | 8 +- .../software/SPDF/model/api/PDFFile.java | 4 +- .../model/api/PDFWithImageFormatRequest.java | 8 +- .../SPDF/model/api/PDFWithPageNums.java | 50 +- .../SPDF/model/api/PDFWithPageSize.java | 13 +- .../model/api/SplitPdfBySectionsRequest.java | 6 +- .../api/converters/ConvertToImageRequest.java | 16 +- .../api/converters/ConvertToPdfRequest.java | 16 +- .../converters/PdfToPresentationRequest.java | 7 +- .../api/converters/PdfToTextOrRTFRequest.java | 7 +- .../api/converters/PdfToWordRequest.java | 7 +- .../model/api/converters/UrlToPdfRequest.java | 1 + .../SPDF/model/api/extract/PDFFilePage.java | 7 +- .../model/api/filter/ContainsTextRequest.java | 3 +- .../model/api/filter/FileSizeRequest.java | 5 +- .../model/api/filter/PageRotationRequest.java | 4 +- .../model/api/filter/PageSizeRequest.java | 5 +- .../SPDF/model/api/general/CropPdfForm.java | 14 +- .../general/MergeMultiplePagesRequest.java | 11 +- .../model/api/general/MergePdfsRequest.java | 18 +- .../model/api/general/OverlayPdfsRequest.java | 20 +- .../api/general/RearrangePagesRequest.java | 25 +- .../model/api/general/RotatePDFRequest.java | 8 +- .../model/api/general/ScalePagesRequest.java | 7 +- .../general/SplitPdfBySizeOrCountRequest.java | 18 +- .../model/api/misc/AddPageNumbersRequest.java | 11 +- .../model/api/misc/AutoSplitPdfRequest.java | 9 +- .../model/api/misc/ExtractHeaderRequest.java | 9 +- .../api/misc/ExtractImageScansRequest.java | 28 +- .../SPDF/model/api/misc/MetadataRequest.java | 7 +- .../model/api/misc/OptimizePdfRequest.java | 9 +- .../model/api/misc/OverlayImageRequest.java | 15 +- .../api/misc/ProcessPdfWithOcrRequest.java | 12 +- .../api/misc/RemoveBlankPagesRequest.java | 13 +- .../api/security/AddPasswordRequest.java | 26 +- .../api/security/AddWatermarkRequest.java | 15 +- .../api/security/PDFPasswordRequest.java | 3 +- .../model/api/security/RedactPdfRequest.java | 3 +- .../api/security/SanitizePdfRequest.java | 3 +- .../api/security/SignPDFWithCertRequest.java | 15 +- .../software/SPDF/pdf/ImageFinder.java | 131 ++-- .../software/SPDF/pdf/TextFinder.java | 135 ++-- .../SPDF/repository/AuthorityRepository.java | 4 +- .../repository/JPATokenRepositoryImpl.java | 6 +- .../repository/PersistentLoginRepository.java | 3 +- .../SPDF/repository/UserRepository.java | 2 +- .../software/SPDF/utils/ErrorUtils.java | 1 - .../software/SPDF/utils/FileToPdf.java | 151 ++-- .../software/SPDF/utils/GeneralUtils.java | 267 ++++--- .../SPDF/utils/ImageProcessingUtils.java | 43 +- .../SPDF/utils/PDFManipulationUtils.java | 4 +- .../software/SPDF/utils/PDFToFile.java | 45 +- .../software/SPDF/utils/PdfUtils.java | 171 +++-- .../software/SPDF/utils/ProcessExecutor.java | 147 ++-- .../software/SPDF/utils/PropertyConfigs.java | 55 +- .../software/SPDF/utils/RequestUriUtils.java | 18 +- .../software/SPDF/utils/WebResponseUtils.java | 79 +- .../resources/static/pdfjs/cmaps/CNS2-V.bcmap | 4 +- .../static/pdfjs/cmaps/ETenms-B5-H.bcmap | 4 +- 155 files changed, 5539 insertions(+), 4767 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java index 7f4fc160..6d32adc3 100644 --- a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java +++ b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java @@ -22,14 +22,14 @@ public class LibreOfficeListener { private Process process; - private LibreOfficeListener() { - } + private LibreOfficeListener() {} private boolean isListenerRunning() { try { System.out.println("waiting for listener to start"); Socket socket = new Socket(); - socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second + socket.connect( + new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second socket.close(); return true; } catch (IOException e) { @@ -49,21 +49,22 @@ public class LibreOfficeListener { // Start a background thread to monitor the activity timeout executorService = Executors.newSingleThreadExecutor(); - executorService.submit(() -> { - while (true) { - long idleTime = System.currentTimeMillis() - lastActivityTime; - if (idleTime >= ACTIVITY_TIMEOUT) { - // If there has been no activity for too long, tear down the listener - process.destroy(); - break; - } - try { - Thread.sleep(5000); // Check for inactivity every 5 seconds - } catch (InterruptedException e) { - break; - } - } - }); + executorService.submit( + () -> { + while (true) { + long idleTime = System.currentTimeMillis() - lastActivityTime; + if (idleTime >= ACTIVITY_TIMEOUT) { + // If there has been no activity for too long, tear down the listener + process.destroy(); + break; + } + try { + Thread.sleep(5000); // Check for inactivity every 5 seconds + } catch (InterruptedException e) { + break; + } + } + }); // Wait for the listener to start up long startTime = System.currentTimeMillis(); @@ -92,5 +93,4 @@ public class LibreOfficeListener { process.destroy(); } } - } diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index a7ce7f14..ef3733c6 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -13,13 +13,12 @@ import org.springframework.scheduling.annotation.EnableScheduling; import jakarta.annotation.PostConstruct; import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.utils.GeneralUtils; -@SpringBootApplication +@SpringBootApplication @EnableScheduling public class SPdfApplication { - @Autowired - private Environment env; + @Autowired private Environment env; @PostConstruct public void init() { @@ -44,21 +43,24 @@ public class SPdfApplication { } public static void main(String[] args) { - SpringApplication app = new SpringApplication(SPdfApplication.class); - app.addInitializers(new ConfigInitializer()); - if (Files.exists(Paths.get("configs/settings.yml"))) { - app.setDefaultProperties(Collections.singletonMap("spring.config.additional-location", "file:configs/settings.yml")); + SpringApplication app = new SpringApplication(SPdfApplication.class); + app.addInitializers(new ConfigInitializer()); + if (Files.exists(Paths.get("configs/settings.yml"))) { + app.setDefaultProperties( + Collections.singletonMap( + "spring.config.additional-location", "file:configs/settings.yml")); } else { - System.out.println("External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); + System.out.println( + "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); } app.run(args); try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } GeneralUtils.createDir("customFiles/static/"); GeneralUtils.createDir("customFiles/templates/"); diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index faf85b28..273de957 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -5,13 +5,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import stirling.software.SPDF.model.ApplicationProperties; + @Configuration public class AppConfig { - - @Autowired - ApplicationProperties applicationProperties; - + @Autowired ApplicationProperties applicationProperties; + @Bean(name = "loginEnabled") public boolean loginEnabled() { return applicationProperties.getSecurity().getEnableLogin(); @@ -19,7 +18,7 @@ public class AppConfig { @Bean(name = "appName") public String appName() { - String homeTitle = applicationProperties.getUi().getAppName(); + String homeTitle = applicationProperties.getUi().getAppName(); return (homeTitle != null) ? homeTitle : "Stirling PDF"; } @@ -31,28 +30,31 @@ public class AppConfig { @Bean(name = "homeText") public String homeText() { - return (applicationProperties.getUi().getHomeDescription() != null) ? applicationProperties.getUi().getHomeDescription() : "null"; + return (applicationProperties.getUi().getHomeDescription() != null) + ? applicationProperties.getUi().getHomeDescription() + : "null"; } - @Bean(name = "navBarText") public String navBarText() { - String defaultNavBar = applicationProperties.getUi().getAppNameNavbar() != null ? applicationProperties.getUi().getAppNameNavbar() : applicationProperties.getUi().getAppName(); + String defaultNavBar = + applicationProperties.getUi().getAppNameNavbar() != null + ? applicationProperties.getUi().getAppNameNavbar() + : applicationProperties.getUi().getAppName(); return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; } - + @Bean(name = "enableAlphaFunctionality") - public boolean enableAlphaFunctionality() { - return applicationProperties.getSystem().getEnableAlphaFunctionality() != null ? applicationProperties.getSystem().getEnableAlphaFunctionality() : false; + public boolean enableAlphaFunctionality() { + return applicationProperties.getSystem().getEnableAlphaFunctionality() != null + ? applicationProperties.getSystem().getEnableAlphaFunctionality() + : false; } - - @Bean(name = "rateLimit") + + @Bean(name = "rateLimit") public boolean rateLimit() { String appName = System.getProperty("rateLimit"); - if (appName == null) - appName = System.getenv("rateLimit"); + if (appName == null) appName = System.getenv("rateLimit"); return (appName != null) ? Boolean.valueOf(appName) : false; } - - -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index d1c6a03b..9230a0a0 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -15,10 +15,9 @@ import stirling.software.SPDF.model.ApplicationProperties; @Configuration public class Beans implements WebMvcConfigurer { - - @Autowired - ApplicationProperties applicationProperties; - + + @Autowired ApplicationProperties applicationProperties; + @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); @@ -35,25 +34,26 @@ public class Beans implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); - - + String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); - Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set + Locale defaultLocale = + Locale.UK; // Fallback to UK locale if environment variable is not set if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { Locale tempLocale = Locale.forLanguageTag(appLocaleEnv); String tempLanguageTag = tempLocale.toLanguageTag(); - if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { + if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { defaultLocale = tempLocale; } else { - tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_","-")); + tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-")); tempLanguageTag = tempLocale.toLanguageTag(); if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { defaultLocale = tempLocale; } else { - System.err.println("Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); + System.err.println( + "Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); } } } @@ -61,5 +61,4 @@ public class Beans implements WebMvcConfigurer { slr.setDefaultLocale(defaultLocale); return slr; } - } diff --git a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java index 894d50d8..472fb951 100644 --- a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java @@ -13,56 +13,62 @@ import jakarta.servlet.http.HttpServletResponse; public class CleanUrlInterceptor implements HandlerInterceptor { - private static final List ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); + private static final List ALLOWED_PARAMS = + Arrays.asList( + "lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - String queryString = request.getQueryString(); - if (queryString != null && !queryString.isEmpty()) { - String requestURI = request.getRequestURI(); - Map parameters = new HashMap<>(); + @Override + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + String requestURI = request.getRequestURI(); + Map parameters = new HashMap<>(); - // Keep only the allowed parameters - String[] queryParameters = queryString.split("&"); - for (String param : queryParameters) { - String[] keyValue = param.split("="); - if (keyValue.length != 2) { - continue; - } - if (ALLOWED_PARAMS.contains(keyValue[0])) { - parameters.put(keyValue[0], keyValue[1]); - } - } + // Keep only the allowed parameters + String[] queryParameters = queryString.split("&"); + for (String param : queryParameters) { + String[] keyValue = param.split("="); + if (keyValue.length != 2) { + continue; + } + if (ALLOWED_PARAMS.contains(keyValue[0])) { + parameters.put(keyValue[0], keyValue[1]); + } + } - // If there are any parameters that are not allowed - if (parameters.size() != queryParameters.length) { - // Construct new query string - StringBuilder newQueryString = new StringBuilder(); - for (Map.Entry entry : parameters.entrySet()) { - if (newQueryString.length() > 0) { - newQueryString.append("&"); - } - newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); - } + // If there are any parameters that are not allowed + if (parameters.size() != queryParameters.length) { + // Construct new query string + StringBuilder newQueryString = new StringBuilder(); + for (Map.Entry entry : parameters.entrySet()) { + if (newQueryString.length() > 0) { + newQueryString.append("&"); + } + newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); + } - // Redirect to the URL with only allowed query parameters - String redirectUrl = requestURI + "?" + newQueryString; - response.sendRedirect(redirectUrl); - return false; - } - } - return true; - } + // Redirect to the URL with only allowed query parameters + String redirectUrl = requestURI + "?" + newQueryString; + response.sendRedirect(redirectUrl); + return false; + } + } + return true; + } - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) { - } + @Override + public void postHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler, + ModelAndView modelAndView) {} - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, - Exception ex) { - } + @Override + public void afterCompletion( + HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) {} } diff --git a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java index 862f5718..6435c955 100644 --- a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java +++ b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java @@ -19,111 +19,125 @@ import java.util.stream.Collectors; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -public class ConfigInitializer implements ApplicationContextInitializer { +public class ConfigInitializer + implements ApplicationContextInitializer { - @Override - public void initialize(ConfigurableApplicationContext applicationContext) { - try { - ensureConfigExists(); - } catch (IOException e) { - throw new RuntimeException("Failed to initialize application configuration", e); - } - } + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + try { + ensureConfigExists(); + } catch (IOException e) { + throw new RuntimeException("Failed to initialize application configuration", e); + } + } - public void ensureConfigExists() throws IOException { - // Define the path to the external config directory - Path destPath = Paths.get("configs", "settings.yml"); + public void ensureConfigExists() throws IOException { + // Define the path to the external config directory + Path destPath = Paths.get("configs", "settings.yml"); - // Check if the file already exists - if (Files.notExists(destPath)) { - // Ensure the destination directory exists - Files.createDirectories(destPath.getParent()); + // Check if the file already exists + if (Files.notExists(destPath)) { + // Ensure the destination directory exists + Files.createDirectories(destPath.getParent()); - // Copy the resource from classpath to the external directory - try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { - if (in != null) { - Files.copy(in, destPath); - } else { - throw new FileNotFoundException("Resource file not found: settings.yml.template"); - } - } - } else { - // If user file exists, we need to merge it with the template from the classpath - List templateLines; - try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { - templateLines = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines() - .collect(Collectors.toList()); - } + // Copy the resource from classpath to the external directory + try (InputStream in = + getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { + if (in != null) { + Files.copy(in, destPath); + } else { + throw new FileNotFoundException( + "Resource file not found: settings.yml.template"); + } + } + } else { + // If user file exists, we need to merge it with the template from the classpath + List templateLines; + try (InputStream in = + getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { + templateLines = + new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.toList()); + } - mergeYamlFiles(templateLines, destPath, destPath); - } - } + mergeYamlFiles(templateLines, destPath, destPath); + } + } - public void mergeYamlFiles(List templateLines, Path userFilePath, Path outputPath) throws IOException { - List userLines = Files.readAllLines(userFilePath); - List mergedLines = new ArrayList<>(); - boolean insideAutoGenerated = false; - boolean beforeFirstKey = true; + public void mergeYamlFiles(List templateLines, Path userFilePath, Path outputPath) + throws IOException { + List userLines = Files.readAllLines(userFilePath); + List mergedLines = new ArrayList<>(); + boolean insideAutoGenerated = false; + boolean beforeFirstKey = true; - Function isCommented = line -> line.trim().startsWith("#"); - Function extractKey = line -> { - String[] parts = line.split(":"); - return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; - }; + Function isCommented = line -> line.trim().startsWith("#"); + Function extractKey = + line -> { + String[] parts = line.split(":"); + return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; + }; - Set userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet()); + Set userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet()); - for (String line : templateLines) { - String key = extractKey.apply(line); + for (String line : templateLines) { + String key = extractKey.apply(line); - if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) { - insideAutoGenerated = true; - mergedLines.add(line); - continue; - } else if (insideAutoGenerated && line.trim().isEmpty()) { - insideAutoGenerated = false; - mergedLines.add(line); - continue; - } + if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) { + insideAutoGenerated = true; + mergedLines.add(line); + continue; + } else if (insideAutoGenerated && line.trim().isEmpty()) { + insideAutoGenerated = false; + mergedLines.add(line); + continue; + } - if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) { - // Handle top comments and empty lines before the first key. - mergedLines.add(line); - continue; - } + if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) { + // Handle top comments and empty lines before the first key. + mergedLines.add(line); + continue; + } - if (!key.isEmpty()) - beforeFirstKey = false; + if (!key.isEmpty()) beforeFirstKey = false; - if (userKeys.contains(key)) { - // If user has any version (commented or uncommented) of this key, skip the - // template line - Optional userValue = userLines.stream() - .filter(l -> extractKey.apply(l).equalsIgnoreCase(key) && !isCommented.apply(l)).findFirst(); - if (userValue.isPresent()) - mergedLines.add(userValue.get()); - continue; - } + if (userKeys.contains(key)) { + // If user has any version (commented or uncommented) of this key, skip the + // template line + Optional userValue = + userLines.stream() + .filter( + l -> + extractKey.apply(l).equalsIgnoreCase(key) + && !isCommented.apply(l)) + .findFirst(); + if (userValue.isPresent()) mergedLines.add(userValue.get()); + continue; + } - if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) { - mergedLines.add(line); // If line is commented, empty or key not present in user's file, retain the - // template line - continue; - } - } + if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) { + mergedLines.add( + line); // If line is commented, empty or key not present in user's file, + // retain the + // template line + continue; + } + } - // Add any additional uncommented user lines that are not present in the - // template - for (String userLine : userLines) { - String userKey = extractKey.apply(userLine); - boolean isPresentInTemplate = templateLines.stream().map(extractKey) - .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey)); - if (!isPresentInTemplate && !isCommented.apply(userLine)) { - mergedLines.add(userLine); - } - } + // Add any additional uncommented user lines that are not present in the + // template + for (String userLine : userLines) { + String userKey = extractKey.apply(userLine); + boolean isPresentInTemplate = + templateLines.stream() + .map(extractKey) + .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey)); + if (!isPresentInTemplate && !isCommented.apply(userLine)) { + mergedLines.add(userLine); + } + } - Files.write(outputPath, mergedLines, StandardCharsets.UTF_8); - } - -} \ No newline at end of file + Files.write(outputPath, mergedLines, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index ebba9815..ddba4623 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import stirling.software.SPDF.model.ApplicationProperties; + @Service public class EndpointConfiguration { private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); @@ -26,16 +27,16 @@ public class EndpointConfiguration { init(); processEnvironmentConfigs(); } - + public void enableEndpoint(String endpoint) { - endpointStatuses.put(endpoint, true); + endpointStatuses.put(endpoint, true); } public void disableEndpoint(String endpoint) { - if(!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { - logger.info("Disabling {}", endpoint); - endpointStatuses.put(endpoint, false); - } + if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { + logger.info("Disabling {}", endpoint); + endpointStatuses.put(endpoint, false); + } } public boolean isEndpointEnabled(String endpoint) { @@ -66,7 +67,7 @@ public class EndpointConfiguration { } } } - + public void init() { // Adding endpoints to "PageOps" group addEndpointToGroup("PageOps", "remove-pages"); @@ -84,8 +85,7 @@ public class EndpointConfiguration { addEndpointToGroup("PageOps", "split-by-size-or-count"); addEndpointToGroup("PageOps", "overlay-pdf"); addEndpointToGroup("PageOps", "split-pdf-by-sections"); - - + // Adding endpoints to "Convert" group addEndpointToGroup("Convert", "pdf-to-img"); addEndpointToGroup("Convert", "img-to-pdf"); @@ -101,8 +101,7 @@ public class EndpointConfiguration { addEndpointToGroup("Convert", "url-to-pdf"); addEndpointToGroup("Convert", "markdown-to-pdf"); addEndpointToGroup("Convert", "pdf-to-csv"); - - + // Adding endpoints to "Security" group addEndpointToGroup("Security", "add-password"); addEndpointToGroup("Security", "remove-password"); @@ -111,8 +110,7 @@ public class EndpointConfiguration { addEndpointToGroup("Security", "cert-sign"); addEndpointToGroup("Security", "sanitize-pdf"); addEndpointToGroup("Security", "auto-redact"); - - + // Adding endpoints to "Other" group addEndpointToGroup("Other", "ocr-pdf"); addEndpointToGroup("Other", "add-image"); @@ -130,10 +128,8 @@ public class EndpointConfiguration { addEndpointToGroup("Other", "auto-rename"); addEndpointToGroup("Other", "get-info-on-pdf"); addEndpointToGroup("Other", "show-javascript"); - - - - //CLI + + // CLI addEndpointToGroup("CLI", "compress-pdf"); addEndpointToGroup("CLI", "extract-image-scans"); addEndpointToGroup("CLI", "remove-blanks"); @@ -149,19 +145,18 @@ public class EndpointConfiguration { addEndpointToGroup("CLI", "ocr-pdf"); addEndpointToGroup("CLI", "html-to-pdf"); addEndpointToGroup("CLI", "url-to-pdf"); - - - //python + + // python addEndpointToGroup("Python", "extract-image-scans"); addEndpointToGroup("Python", "remove-blanks"); addEndpointToGroup("Python", "html-to-pdf"); addEndpointToGroup("Python", "url-to-pdf"); - - //openCV + + // openCV addEndpointToGroup("OpenCV", "extract-image-scans"); addEndpointToGroup("OpenCV", "remove-blanks"); - //LibreOffice + // LibreOffice addEndpointToGroup("LibreOffice", "repair"); addEndpointToGroup("LibreOffice", "file-to-pdf"); addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); @@ -170,14 +165,13 @@ public class EndpointConfiguration { addEndpointToGroup("LibreOffice", "pdf-to-text"); addEndpointToGroup("LibreOffice", "pdf-to-html"); addEndpointToGroup("LibreOffice", "pdf-to-xml"); - - - //OCRmyPDF + + // OCRmyPDF addEndpointToGroup("OCRmyPDF", "compress-pdf"); addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); addEndpointToGroup("OCRmyPDF", "ocr-pdf"); - - //Java + + // Java addEndpointToGroup("Java", "merge-pdfs"); addEndpointToGroup("Java", "remove-pages"); addEndpointToGroup("Java", "split-pdfs"); @@ -210,16 +204,14 @@ public class EndpointConfiguration { addEndpointToGroup("Java", "split-by-size-or-count"); addEndpointToGroup("Java", "overlay-pdf"); addEndpointToGroup("Java", "split-pdf-by-sections"); - - //Javascript + + // Javascript addEndpointToGroup("Javascript", "pdf-organizer"); addEndpointToGroup("Javascript", "sign"); addEndpointToGroup("Javascript", "compare"); addEndpointToGroup("Javascript", "adjust-contrast"); - - } - + private void processEnvironmentConfigs() { List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); @@ -236,6 +228,4 @@ public class EndpointConfiguration { } } } - } - diff --git a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java index 77191a41..d408b9ea 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java @@ -10,11 +10,11 @@ import jakarta.servlet.http.HttpServletResponse; @Component public class EndpointInterceptor implements HandlerInterceptor { - @Autowired - private EndpointConfiguration endpointConfiguration; + @Autowired private EndpointConfiguration endpointConfiguration; @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI = request.getRequestURI(); if (!endpointConfiguration.isEndpointEnabled(requestURI)) { @@ -23,4 +23,4 @@ public class EndpointInterceptor implements HandlerInterceptor { } return true; } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java index 1cdc99e3..3877c566 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.config; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,4 +22,4 @@ public class MetricsConfig { } }; } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java index 9abb68bf..9207fd07 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java @@ -8,6 +8,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -16,35 +17,48 @@ import jakarta.servlet.http.HttpServletResponse; @Component public class MetricsFilter extends OncePerRequestFilter { - private final MeterRegistry meterRegistry; + private final MeterRegistry meterRegistry; - @Autowired - public MetricsFilter(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } + @Autowired + public MetricsFilter(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + } - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - String uri = request.getRequestURI(); + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String uri = request.getRequestURI(); - // System.out.println("uri="+uri + ", method=" + request.getMethod() ); - // Ignore static resources - if (!(uri.startsWith("/js") || uri.startsWith("/v1/api-docs") || uri.endsWith("robots.txt") - || uri.startsWith("/images") || uri.startsWith("/images")|| uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".map") - || uri.endsWith(".svg") || uri.endsWith(".js") || uri.contains("swagger") - || uri.startsWith("/api/v1/info") || uri.startsWith("/site.webmanifest") || uri.startsWith("/fonts") || uri.startsWith("/pdfjs") )) { - - - - Counter counter = Counter.builder("http.requests").tag("uri", uri).tag("method", request.getMethod()) - .register(meterRegistry); + // System.out.println("uri="+uri + ", method=" + request.getMethod() ); + // Ignore static resources + if (!(uri.startsWith("/js") + || uri.startsWith("/v1/api-docs") + || uri.endsWith("robots.txt") + || uri.startsWith("/images") + || uri.startsWith("/images") + || uri.endsWith(".png") + || uri.endsWith(".ico") + || uri.endsWith(".css") + || uri.endsWith(".map") + || uri.endsWith(".svg") + || uri.endsWith(".js") + || uri.contains("swagger") + || uri.startsWith("/api/v1/info") + || uri.startsWith("/site.webmanifest") + || uri.startsWith("/fonts") + || uri.startsWith("/pdfjs"))) { - counter.increment(); - // System.out.println("Counted"); - } + Counter counter = + Counter.builder("http.requests") + .tag("uri", uri) + .tag("method", request.getMethod()) + .register(meterRegistry); - filterChain.doFilter(request, response); - } + counter.increment(); + // System.out.println("Counted"); + } + filterChain.doFilter(request, response); + } } diff --git a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index 5dba40d0..a852bb1a 100644 --- a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -9,34 +9,45 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; + import stirling.software.SPDF.model.ApplicationProperties; @Configuration public class OpenApiConfig { - @Autowired - ApplicationProperties applicationProperties; + @Autowired ApplicationProperties applicationProperties; - @Bean - public OpenAPI customOpenAPI() { - String version = getClass().getPackage().getImplementationVersion(); - if (version == null) { - version = "1.0.0"; // default version if all else fails - } + @Bean + public OpenAPI customOpenAPI() { + String version = getClass().getPackage().getImplementationVersion(); + if (version == null) { + version = "1.0.0"; // default version if all else fails + } - SecurityScheme apiKeyScheme = new SecurityScheme().type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER) - .name("X-API-KEY"); - if (!applicationProperties.getSecurity().getEnableLogin()) { - return new OpenAPI().components(new Components()) - .info(new Info().title("Stirling PDF API").version(version).description( - "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); - } else { - return new OpenAPI().components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) - .info(new Info().title("Stirling PDF API").version(version).description( - "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) - .addSecurityItem(new SecurityRequirement().addList("apiKey")); - } - - } - -} \ No newline at end of file + SecurityScheme apiKeyScheme = + new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("X-API-KEY"); + if (!applicationProperties.getSecurity().getEnableLogin()) { + return new OpenAPI() + .components(new Components()) + .info( + new Info() + .title("Stirling PDF API") + .version(version) + .description( + "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); + } else { + return new OpenAPI() + .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) + .info( + new Info() + .title("Stirling PDF API") + .version(version) + .description( + "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) + .addSecurityItem(new SecurityRequirement().addList("apiKey")); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java index 77b69c88..07644b30 100644 --- a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java +++ b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java @@ -1,6 +1,5 @@ package stirling.software.SPDF.config; - import java.time.LocalDateTime; import org.springframework.context.ApplicationListener; @@ -17,4 +16,3 @@ public class StartupApplicationListener implements ApplicationListener createPropertySource(String name, EncodedResource encodedResource) - throws IOException { + public PropertySource createPropertySource(String name, EncodedResource encodedResource) + throws IOException { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(encodedResource.getResource()); Properties properties = factory.getObject(); - return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties); + return new PropertiesPropertySource( + encodedResource.getResource().getFilename(), properties); } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java index 397a8a70..6c2a05d3 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java @@ -12,36 +12,38 @@ import org.springframework.stereotype.Component; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + @Component public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - @Autowired - private final LoginAttemptService loginAttemptService; + + @Autowired private final LoginAttemptService loginAttemptService; @Autowired public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) { this.loginAttemptService = loginAttemptService; } - + @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) - throws IOException, ServletException { - String ip = request.getRemoteAddr(); + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { + String ip = request.getRemoteAddr(); logger.error("Failed login attempt from IP: " + ip); - + String username = request.getParameter("username"); - if(loginAttemptService.loginAttemptCheck(username)) { - setDefaultFailureUrl("/login?error=locked"); - + if (loginAttemptService.loginAttemptCheck(username)) { + setDefaultFailureUrl("/login?error=locked"); + } else { - if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { - setDefaultFailureUrl("/login?error=badcredentials"); - } else if (exception.getClass().isAssignableFrom(LockedException.class)) { - setDefaultFailureUrl("/login?error=locked"); - } + if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { + setDefaultFailureUrl("/login?error=badcredentials"); + } else if (exception.getClass().isAssignableFrom(LockedException.class)) { + setDefaultFailureUrl("/login?error=locked"); + } } - - + super.onAuthenticationFailure(request, response, exception); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java index cd2217e1..d14466ea 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java @@ -15,30 +15,33 @@ import jakarta.servlet.http.HttpSession; import stirling.software.SPDF.utils.RequestUriUtils; @Component -public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { +public class CustomAuthenticationSuccessHandler + extends SavedRequestAwareAuthenticationSuccessHandler { - @Autowired - private LoginAttemptService loginAttemptService; + @Autowired private LoginAttemptService loginAttemptService; @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { - String username = request.getParameter("username"); + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws ServletException, IOException { + String username = request.getParameter("username"); loginAttemptService.loginSucceeded(username); - - + // Get the saved request HttpSession session = request.getSession(false); - SavedRequest savedRequest = session != null ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") : null; - if (savedRequest != null && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) { + SavedRequest savedRequest = + session != null + ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") + : null; + if (savedRequest != null + && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) { // Redirect to the original destination super.onAuthenticationSuccess(request, response, authentication); } else { // Redirect to the root URL (considering context path) getRedirectStrategy().sendRedirect(request, response, "/"); } - - //super.onAuthenticationSuccess(request, response, authentication); - } - + // super.onAuthenticationSuccess(request, response, authentication); + } } diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java index 77db2cd4..021cdc31 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java @@ -20,33 +20,38 @@ import stirling.software.SPDF.repository.UserRepository; @Service public class CustomUserDetailsService implements UserDetailsService { - @Autowired - private UserRepository userRepository; + @Autowired private UserRepository userRepository; + + @Autowired private LoginAttemptService loginAttemptService; - @Autowired - private LoginAttemptService loginAttemptService; - @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username)); + User user = + userRepository + .findByUsername(username) + .orElseThrow( + () -> + new UsernameNotFoundException( + "No user found with username: " + username)); if (loginAttemptService.isBlocked(username)) { - throw new LockedException("Your account has been locked due to too many failed login attempts."); + throw new LockedException( + "Your account has been locked due to too many failed login attempts."); } - + return new org.springframework.security.core.userdetails.User( - user.getUsername(), - user.getPassword(), - user.isEnabled(), - true, true, true, - getAuthorities(user.getAuthorities()) - ); + user.getUsername(), + user.getPassword(), + user.isEnabled(), + true, + true, + true, + getAuthorities(user.getAuthorities())); } private Collection getAuthorities(Set authorities) { return authorities.stream() - .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) - .collect(Collectors.toList()); + .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) + .collect(Collectors.toList()); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java index 65acf148..b272327a 100644 --- a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java @@ -19,16 +19,16 @@ import stirling.software.SPDF.utils.RequestUriUtils; @Component public class FirstLoginFilter extends OncePerRequestFilter { - - @Autowired - @Lazy - private UserService userService; - + + @Autowired @Lazy private UserService userService; + @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String method = request.getMethod(); - String requestURI = request.getRequestURI(); - // Check if the request is for static resources + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String method = request.getMethod(); + String requestURI = request.getRequestURI(); + // Check if the request is for static resources boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI); // If it's a static resource, just continue the filter chain and skip the logic below @@ -36,11 +36,14 @@ public class FirstLoginFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); return; } - + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { Optional user = userService.findByUsername(authentication.getName()); - if ("GET".equalsIgnoreCase(method) && user.isPresent() && user.get().isFirstLogin() && !"/change-creds".equals(requestURI)) { + if ("GET".equalsIgnoreCase(method) + && user.isPresent() + && user.get().isFirstLogin() + && !"/change-creds".equals(requestURI)) { response.sendRedirect("/change-creds"); return; } diff --git a/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java index 03e34b57..b79cd586 100644 --- a/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.config.security; + import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -13,51 +14,53 @@ import stirling.software.SPDF.utils.RequestUriUtils; public class IPRateLimitingFilter implements Filter { - private final ConcurrentHashMap requestCounts = new ConcurrentHashMap<>(); + private final ConcurrentHashMap requestCounts = + new ConcurrentHashMap<>(); private final ConcurrentHashMap getCounts = new ConcurrentHashMap<>(); private final int maxRequests; private final int maxGetRequests; - + public IPRateLimitingFilter(int maxRequests, int maxGetRequests) { this.maxRequests = maxRequests; this.maxGetRequests = maxGetRequests; } @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (request instanceof HttpServletRequest) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - String method = httpRequest.getMethod(); - String requestURI = httpRequest.getRequestURI(); - // Check if the request is for static resources - boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI); + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (request instanceof HttpServletRequest) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String method = httpRequest.getMethod(); + String requestURI = httpRequest.getRequestURI(); + // Check if the request is for static resources + boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI); - // If it's a static resource, just continue the filter chain and skip the logic below - if (isStaticResource) { - chain.doFilter(request, response); - return; - } - - String clientIp = request.getRemoteAddr(); - requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0)); - if (!"GET".equalsIgnoreCase(method)) { - - if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) { - // Handle limit exceeded (e.g., send error response) - response.getWriter().write("Rate limit exceeded"); - return; - } - } else { - if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) { - // Handle limit exceeded (e.g., send error response) - response.getWriter().write("GET Rate limit exceeded"); - return; - } - } - } - chain.doFilter(request, response); + // If it's a static resource, just continue the filter chain and skip the logic below + if (isStaticResource) { + chain.doFilter(request, response); + return; + } + + String clientIp = request.getRemoteAddr(); + requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0)); + if (!"GET".equalsIgnoreCase(method)) { + + if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) { + // Handle limit exceeded (e.g., send error response) + response.getWriter().write("Rate limit exceeded"); + return; + } + } else { + if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) { + // Handle limit exceeded (e.g., send error response) + response.getWriter().write("GET Rate limit exceeded"); + return; + } + } + } + chain.doFilter(request, response); } - + public void resetRequestCounts() { requestCounts.clear(); getCounts.clear(); diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java index 5d100dd8..3b396b15 100644 --- a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -13,75 +13,76 @@ import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.Role; + @Component public class InitialSecuritySetup { - @Autowired - private UserService userService; + @Autowired private UserService userService; + @Autowired ApplicationProperties applicationProperties; - @Autowired - ApplicationProperties applicationProperties; - - @PostConstruct - public void init() { - if (!userService.hasUsers()) { - - - String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername(); - String initialPassword = applicationProperties.getSecurity().getInitialLogin().getPassword(); - if (initialUsername != null && initialPassword != null) { - userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId()); - } else { - initialUsername = "admin"; - initialPassword = "stirling"; - userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId(), true); - } - } - if(!userService.usernameExists(Role.INTERNAL_API_USER.getRoleId())) { - userService.saveUser(Role.INTERNAL_API_USER.getRoleId(), UUID.randomUUID().toString(), Role.INTERNAL_API_USER.getRoleId()); - userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); - } - } + @PostConstruct + public void init() { + if (!userService.hasUsers()) { + String initialUsername = + applicationProperties.getSecurity().getInitialLogin().getUsername(); + String initialPassword = + applicationProperties.getSecurity().getInitialLogin().getPassword(); + if (initialUsername != null && initialPassword != null) { + userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId()); + } else { + initialUsername = "admin"; + initialPassword = "stirling"; + userService.saveUser( + initialUsername, initialPassword, Role.ADMIN.getRoleId(), true); + } + } + if (!userService.usernameExists(Role.INTERNAL_API_USER.getRoleId())) { + userService.saveUser( + Role.INTERNAL_API_USER.getRoleId(), + UUID.randomUUID().toString(), + Role.INTERNAL_API_USER.getRoleId()); + userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); + } + } + @PostConstruct + public void initSecretKey() throws IOException { + String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); + if (secretKey == null || secretKey.isEmpty()) { + secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key + saveKeyToConfig(secretKey); + } + } - @PostConstruct - public void initSecretKey() throws IOException { - String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); - if (secretKey == null || secretKey.isEmpty()) { - secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key - saveKeyToConfig(secretKey); - } - } + private void saveKeyToConfig(String key) throws IOException { + Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml + List lines = Files.readAllLines(path); + boolean keyFound = false; - private void saveKeyToConfig(String key) throws IOException { - Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml - List lines = Files.readAllLines(path); - boolean keyFound = false; + // Search for the existing key to replace it or place to add it + for (int i = 0; i < lines.size(); i++) { + if (lines.get(i).startsWith("AutomaticallyGenerated:")) { + keyFound = true; + if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) { + lines.set(i + 1, " key: " + key); + break; + } else { + lines.add(i + 1, " key: " + key); + break; + } + } + } - // Search for the existing key to replace it or place to add it - for (int i = 0; i < lines.size(); i++) { - if (lines.get(i).startsWith("AutomaticallyGenerated:")) { - keyFound = true; - if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) { - lines.set(i + 1, " key: " + key); - break; - } else { - lines.add(i + 1, " key: " + key); - break; - } - } - } + // If the section doesn't exist, append it + if (!keyFound) { + lines.add("# Automatically Generated Settings (Do Not Edit Directly)"); + lines.add("AutomaticallyGenerated:"); + lines.add(" key: " + key); + } - // If the section doesn't exist, append it - if (!keyFound) { - lines.add("# Automatically Generated Settings (Do Not Edit Directly)"); - lines.add("AutomaticallyGenerated:"); - lines.add(" key: " + key); - } - - // Write back to the file - Files.write(path, lines); - } -} \ No newline at end of file + // Write back to the file + Files.write(path, lines); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java b/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java index 55a84449..40a54ecc 100644 --- a/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java +++ b/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.config.security; + import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -12,39 +13,41 @@ import stirling.software.SPDF.model.AttemptCounter; @Service public class LoginAttemptService { - - @Autowired - ApplicationProperties applicationProperties; - + @Autowired ApplicationProperties applicationProperties; + private int MAX_ATTEMPTS; private long ATTEMPT_INCREMENT_TIME; - - + @PostConstruct public void init() { - MAX_ATTEMPTS = applicationProperties.getSecurity().getLoginAttemptCount(); - ATTEMPT_INCREMENT_TIME = TimeUnit.MINUTES.toMillis(applicationProperties.getSecurity().getLoginResetTimeMinutes()); + MAX_ATTEMPTS = applicationProperties.getSecurity().getLoginAttemptCount(); + ATTEMPT_INCREMENT_TIME = + TimeUnit.MINUTES.toMillis( + applicationProperties.getSecurity().getLoginResetTimeMinutes()); } - - private final ConcurrentHashMap attemptsCache = new ConcurrentHashMap<>(); + + private final ConcurrentHashMap attemptsCache = + new ConcurrentHashMap<>(); public void loginSucceeded(String key) { attemptsCache.remove(key); } public boolean loginAttemptCheck(String key) { - attemptsCache.compute(key, (k, attemptCounter) -> { - if (attemptCounter == null || attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) { - return new AttemptCounter(); - } else { - attemptCounter.increment(); - return attemptCounter; - } - }); + attemptsCache.compute( + key, + (k, attemptCounter) -> { + if (attemptCounter == null + || attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) { + return new AttemptCounter(); + } else { + attemptCounter.increment(); + return attemptCounter; + } + }); return attemptsCache.get(key).getAttemptCount() >= MAX_ATTEMPTS; } - public boolean isBlocked(String key) { AttemptCounter attemptCounter = attemptsCache.get(key); if (attemptCounter != null) { @@ -52,5 +55,4 @@ public class LoginAttemptService { } return false; } - } diff --git a/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java b/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java index 3ef8ef31..a3641a70 100644 --- a/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java +++ b/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.config.security; + import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -11,7 +12,7 @@ public class RateLimitResetScheduler { this.rateLimitingFilter = rateLimitingFilter; } - @Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday TODO: configurable + @Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday TODO: configurable public void resetRateLimit() { rateLimitingFilter.resetRequestCounts(); } diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index e0b439db..8fd0b401 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -19,104 +19,108 @@ import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import stirling.software.SPDF.repository.JPATokenRepositoryImpl; + @Configuration @EnableWebSecurity() @EnableMethodSecurity public class SecurityConfiguration { - @Autowired - private UserDetailsService userDetailsService; + @Autowired private UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Autowired - @Lazy - private UserService userService; - - @Autowired - @Qualifier("loginEnabled") - public boolean loginEnabledValue; - - @Autowired - private UserAuthenticationFilter userAuthenticationFilter; - + @Autowired @Lazy private UserService userService; @Autowired - private LoginAttemptService loginAttemptService; - - @Autowired - private FirstLoginFilter firstLoginFilter; - + @Qualifier("loginEnabled") public boolean loginEnabledValue; + + @Autowired private UserAuthenticationFilter userAuthenticationFilter; + + @Autowired private LoginAttemptService loginAttemptService; + + @Autowired private FirstLoginFilter firstLoginFilter; + @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - if(loginEnabledValue) { - - http.csrf(csrf -> csrf.disable()); - http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); - http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); - http - .formLogin(formLogin -> formLogin - .loginPage("/login") - .successHandler(new CustomAuthenticationSuccessHandler()) - .defaultSuccessUrl("/") - .failureHandler(new CustomAuthenticationFailureHandler(loginAttemptService)) - .permitAll() - ).requestCache(requestCache -> requestCache - .requestCache(new NullRequestCache()) - ) - .logout(logout -> logout - .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) - .logoutSuccessUrl("/login?logout=true") - .invalidateHttpSession(true) // Invalidate session - .deleteCookies("JSESSIONID", "remember-me") - ).rememberMe(rememberMeConfigurer -> rememberMeConfigurer // Use the configurator directly - .key("uniqueAndSecret") - .tokenRepository(persistentTokenRepository()) - .tokenValiditySeconds(1209600) // 2 weeks - ) - .authorizeHttpRequests(authz -> authz - .requestMatchers(req -> { - String uri = req.getRequestURI(); - String contextPath = req.getContextPath(); + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - // Remove the context path from the URI - String trimmedUri = uri.startsWith(contextPath) ? uri.substring(contextPath.length()) : uri; + if (loginEnabledValue) { - return trimmedUri.startsWith("/login") || trimmedUri.endsWith(".svg") || - trimmedUri.startsWith("/register") || trimmedUri.startsWith("/error") || - trimmedUri.startsWith("/images/") || trimmedUri.startsWith("/public/") || - trimmedUri.startsWith("/css/") || trimmedUri.startsWith("/js/"); - } - ).permitAll() - .anyRequest().authenticated() - ) - .userDetailsService(userDetailsService) - .authenticationProvider(authenticationProvider()); - } else { - http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authz -> authz - .anyRequest().permitAll() - ); - } + http.csrf(csrf -> csrf.disable()); + http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); + http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); + http.formLogin( + formLogin -> + formLogin + .loginPage("/login") + .successHandler( + new CustomAuthenticationSuccessHandler()) + .defaultSuccessUrl("/") + .failureHandler( + new CustomAuthenticationFailureHandler( + loginAttemptService)) + .permitAll()) + .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) + .logout( + logout -> + logout.logoutRequestMatcher( + new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/login?logout=true") + .invalidateHttpSession(true) // Invalidate session + .deleteCookies("JSESSIONID", "remember-me")) + .rememberMe( + rememberMeConfigurer -> + rememberMeConfigurer // Use the configurator directly + .key("uniqueAndSecret") + .tokenRepository(persistentTokenRepository()) + .tokenValiditySeconds(1209600) // 2 weeks + ) + .authorizeHttpRequests( + authz -> + authz.requestMatchers( + req -> { + String uri = req.getRequestURI(); + String contextPath = req.getContextPath(); + + // Remove the context path from the URI + String trimmedUri = + uri.startsWith(contextPath) + ? uri.substring( + contextPath + .length()) + : uri; + + return trimmedUri.startsWith("/login") + || trimmedUri.endsWith(".svg") + || trimmedUri.startsWith( + "/register") + || trimmedUri.startsWith("/error") + || trimmedUri.startsWith("/images/") + || trimmedUri.startsWith("/public/") + || trimmedUri.startsWith("/css/") + || trimmedUri.startsWith("/js/"); + }) + .permitAll() + .anyRequest() + .authenticated()) + .userDetailsService(userDetailsService) + .authenticationProvider(authenticationProvider()); + } else { + http.csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + } return http.build(); } - - - @Bean public IPRateLimitingFilter rateLimitingFilter() { int maxRequestsPerIp = 1000000; // Example limit TODO add config level return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); } - - @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); @@ -124,13 +128,9 @@ public class SecurityConfiguration { authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } - + @Bean public PersistentTokenRepository persistentTokenRepository() { return new JPATokenRepositoryImpl(); } - - - } - diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index ce77e5a4..6b8d047c 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -19,32 +19,28 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import stirling.software.SPDF.model.ApiKeyAuthenticationToken; + @Component public class UserAuthenticationFilter extends OncePerRequestFilter { - @Autowired - private UserDetailsService userDetailsService; + @Autowired private UserDetailsService userDetailsService; + + @Autowired @Lazy private UserService userService; @Autowired - @Lazy - private UserService userService; - - - @Autowired - @Qualifier("loginEnabled") - public boolean loginEnabledValue; + @Qualifier("loginEnabled") public boolean loginEnabledValue; @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { if (!loginEnabledValue) { // If login is not enabled, just pass all requests without authentication filterChain.doFilter(request, response); return; } - String requestURI = request.getRequestURI(); + String requestURI = request.getRequestURI(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // Check for API key in the request headers if no authentication exists @@ -52,15 +48,17 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { String apiKey = request.getHeader("X-API-Key"); if (apiKey != null && !apiKey.trim().isEmpty()) { try { - // Use API key to authenticate. This requires you to have an authentication provider for API keys. - UserDetails userDetails = userService.loadUserByApiKey(apiKey); - if(userDetails == null) - { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); + // Use API key to authenticate. This requires you to have an authentication + // provider for API keys. + UserDetails userDetails = userService.loadUserByApiKey(apiKey); + if (userDetails == null) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("Invalid API Key."); return; - } - authentication = new ApiKeyAuthenticationToken(userDetails, apiKey, userDetails.getAuthorities()); + } + authentication = + new ApiKeyAuthenticationToken( + userDetails, apiKey, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (AuthenticationException e) { // If API key authentication fails, deny the request @@ -73,36 +71,38 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { // If we still don't have any authentication, deny the request if (authentication == null || !authentication.isAuthenticated()) { - String method = request.getMethod(); - String contextPath = request.getContextPath(); - - if ("GET".equalsIgnoreCase(method) && ! (contextPath + "/login").equals(requestURI)) { - response.sendRedirect(contextPath + "/login"); // redirect to the login page - return; + String method = request.getMethod(); + String contextPath = request.getContextPath(); + + if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { + response.sendRedirect(contextPath + "/login"); // redirect to the login page + return; } else { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); - return; + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter() + .write( + "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); + return; } - } + } filterChain.doFilter(request, response); } - + @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String uri = request.getRequestURI(); String contextPath = request.getContextPath(); String[] permitAllPatterns = { - contextPath + "/login", - contextPath + "/register", - contextPath + "/error", - contextPath + "/images/", - contextPath + "/public/", - contextPath + "/css/", - contextPath + "/js/", - contextPath + "/pdfjs/", - contextPath + "/site.webmanifest" + contextPath + "/login", + contextPath + "/register", + contextPath + "/error", + contextPath + "/images/", + contextPath + "/public/", + contextPath + "/css/", + contextPath + "/js/", + contextPath + "/pdfjs/", + contextPath + "/site.webmanifest" }; for (String pattern : permitAllPatterns) { @@ -113,5 +113,4 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { return false; } - } diff --git a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java index f23e5ce3..cadbfb24 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java @@ -20,28 +20,28 @@ import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; import io.github.bucket4j.ConsumptionProbe; import io.github.bucket4j.Refill; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import stirling.software.SPDF.model.Role; + @Component public class UserBasedRateLimitingFilter extends OncePerRequestFilter { - private final Map apiBuckets = new ConcurrentHashMap<>(); + private final Map apiBuckets = new ConcurrentHashMap<>(); private final Map webBuckets = new ConcurrentHashMap<>(); - @Autowired - private UserDetailsService userDetailsService; + @Autowired private UserDetailsService userDetailsService; @Autowired - @Qualifier("rateLimit") - public boolean rateLimit; + @Qualifier("rateLimit") public boolean rateLimit; @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { if (!rateLimit) { // If rateLimit is not enabled, just pass all requests without rate limiting filterChain.doFilter(request, response); @@ -60,7 +60,8 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter { // Check for API key in the request headers String apiKey = request.getHeader("X-API-Key"); if (apiKey != null && !apiKey.trim().isEmpty()) { - identifier = "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames + identifier = + "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames } else { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { @@ -74,14 +75,27 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter { identifier = request.getRemoteAddr(); } - Role userRole = getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); + Role userRole = + getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); if (request.getHeader("X-API-Key") != null) { // It's an API call - processRequest(userRole.getApiCallsPerDay(), identifier, apiBuckets, request, response, filterChain); + processRequest( + userRole.getApiCallsPerDay(), + identifier, + apiBuckets, + request, + response, + filterChain); } else { // It's a Web UI call - processRequest(userRole.getWebCallsPerDay(), identifier, webBuckets, request, response, filterChain); + processRequest( + userRole.getWebCallsPerDay(), + identifier, + webBuckets, + request, + response, + filterChain); } } @@ -98,8 +112,13 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter { throw new IllegalStateException("User does not have a valid role."); } - private void processRequest(int limitPerDay, String identifier, Map buckets, - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + private void processRequest( + int limitPerDay, + String identifier, + Map buckets, + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws IOException, ServletException { Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); @@ -116,10 +135,8 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter { } private Bucket createUserBucket(int limitPerDay) { - Bandwidth limit = Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); + Bandwidth limit = + Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); return Bucket.builder().addLimit(limit).build(); } } - - - diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index 45794d92..986bb16f 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.config.security; + import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -21,38 +22,35 @@ import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.User; import stirling.software.SPDF.repository.UserRepository; -@Service -public class UserService implements UserServiceInterface{ - - @Autowired - private UserRepository userRepository; - @Autowired - private PasswordEncoder passwordEncoder; +@Service +public class UserService implements UserServiceInterface { + + @Autowired private UserRepository userRepository; + + @Autowired private PasswordEncoder passwordEncoder; public Authentication getAuthentication(String apiKey) { User user = getUserByApiKey(apiKey); if (user == null) { throw new UsernameNotFoundException("API key is not valid"); } - + // Convert the user into an Authentication object return new UsernamePasswordAuthenticationToken( - user, // principal (typically the user) - null, // credentials (we don't expose the password or API key here) - getAuthorities(user) // user's authorities (roles/permissions) - ); + user, // principal (typically the user) + null, // credentials (we don't expose the password or API key here) + getAuthorities(user) // user's authorities (roles/permissions) + ); } - + private Collection getAuthorities(User user) { // Convert each Authority object into a SimpleGrantedAuthority object. - return user.getAuthorities().stream() - .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) - .collect(Collectors.toList()); - - + return user.getAuthorities().stream() + .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) + .collect(Collectors.toList()); } - + private String generateApiKey() { String apiKey; do { @@ -62,9 +60,11 @@ public class UserService implements UserServiceInterface{ } public User addApiKeyToUser(String username) { - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); - + User user = + userRepository + .findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + user.setApiKey(generateApiKey()); return userRepository.save(user); } @@ -74,8 +74,10 @@ public class UserService implements UserServiceInterface{ } public String getApiKeyForUser(String username) { - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = + userRepository + .findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); return user.getApiKey(); } @@ -86,27 +88,25 @@ public class UserService implements UserServiceInterface{ public User getUserByApiKey(String apiKey) { return userRepository.findByApiKey(apiKey); } - + public UserDetails loadUserByApiKey(String apiKey) { User userOptional = userRepository.findByApiKey(apiKey); if (userOptional != null) { User user = userOptional; // Convert your User entity to a UserDetails object with authorities return new org.springframework.security.core.userdetails.User( - user.getUsername(), - user.getPassword(), // you might not need this for API key auth - getAuthorities(user) - ); + user.getUsername(), + user.getPassword(), // you might not need this for API key auth + getAuthorities(user)); } - return null; // or throw an exception + return null; // or throw an exception } - public boolean validateApiKeyForUser(String username, String apiKey) { Optional userOpt = userRepository.findByUsername(username); return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); } - + public void saveUser(String username, String password) { User user = new User(); user.setUsername(username); @@ -124,7 +124,7 @@ public class UserService implements UserServiceInterface{ user.setFirstLogin(firstLogin); userRepository.save(user); } - + public void saveUser(String username, String password, String role) { User user = new User(); user.setUsername(username); @@ -134,42 +134,42 @@ public class UserService implements UserServiceInterface{ user.setFirstLogin(false); userRepository.save(user); } - + public void deleteUser(String username) { - Optional userOpt = userRepository.findByUsername(username); - if (userOpt.isPresent()) { - for (Authority authority : userOpt.get().getAuthorities()) { - if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { - return; - } - } - userRepository.delete(userOpt.get()); - } + Optional userOpt = userRepository.findByUsername(username); + if (userOpt.isPresent()) { + for (Authority authority : userOpt.get().getAuthorities()) { + if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { + return; + } + } + userRepository.delete(userOpt.get()); + } } - + public boolean usernameExists(String username) { return userRepository.findByUsername(username).isPresent(); } - + public boolean hasUsers() { return userRepository.count() > 0; } - + public void updateUserSettings(String username, Map updates) { Optional userOpt = userRepository.findByUsername(username); if (userOpt.isPresent()) { User user = userOpt.get(); Map settingsMap = user.getSettings(); - if(settingsMap == null) { - settingsMap = new HashMap(); - } + if (settingsMap == null) { + settingsMap = new HashMap(); + } settingsMap.clear(); settingsMap.putAll(updates); user.setSettings(settingsMap); userRepository.save(user); - } + } } public Optional findByUsername(String username) { @@ -185,13 +185,12 @@ public class UserService implements UserServiceInterface{ user.setPassword(passwordEncoder.encode(newPassword)); userRepository.save(user); } - + public void changeFirstUse(User user, boolean firstUse) { user.setFirstLogin(firstUse); userRepository.save(user); } - - + public boolean isPasswordCorrect(User user, String currentPassword) { return passwordEncoder.matches(currentPassword, user.getPassword()); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/CropController.java b/src/main/java/stirling/software/SPDF/controller/api/CropController.java index d2723cce..a547d89f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/CropController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/CropController.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.CropPdfForm; import stirling.software.SPDF.utils.WebResponseUtils; @@ -28,59 +29,62 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "General", description = "General APIs") public class CropController { - private static final Logger logger = LoggerFactory.getLogger(CropController.class); + private static final Logger logger = LoggerFactory.getLogger(CropController.class); - @PostMapping(value = "/crop", consumes = "multipart/form-data") - @Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") - public ResponseEntity cropPdf(@ModelAttribute CropPdfForm form) - throws IOException { + @PostMapping(value = "/crop", consumes = "multipart/form-data") + @Operation( + summary = "Crops a PDF document", + description = + "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") + public ResponseEntity cropPdf(@ModelAttribute CropPdfForm form) throws IOException { + PDDocument sourceDocument = + PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes())); - + PDDocument newDocument = new PDDocument(); -PDDocument sourceDocument = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes())); + int totalPages = sourceDocument.getNumberOfPages(); -PDDocument newDocument = new PDDocument(); + LayerUtility layerUtility = new LayerUtility(newDocument); -int totalPages = sourceDocument.getNumberOfPages(); + for (int i = 0; i < totalPages; i++) { + PDPage sourcePage = sourceDocument.getPage(i); -LayerUtility layerUtility = new LayerUtility(newDocument); + // Create a new page with the size of the source page + PDPage newPage = new PDPage(sourcePage.getMediaBox()); + newDocument.addPage(newPage); + PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); -for (int i = 0; i < totalPages; i++) { - PDPage sourcePage = sourceDocument.getPage(i); - - // Create a new page with the size of the source page - PDPage newPage = new PDPage(sourcePage.getMediaBox()); - newDocument.addPage(newPage); - PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); + // Import the source page as a form XObject + PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); - // Import the source page as a form XObject - PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); + contentStream.saveGraphicsState(); - contentStream.saveGraphicsState(); - - // Define the crop area - contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight()); - contentStream.clip(); + // Define the crop area + contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight()); + contentStream.clip(); - // Draw the entire formXObject - contentStream.drawForm(formXObject); + // Draw the entire formXObject + contentStream.drawForm(formXObject); - contentStream.restoreGraphicsState(); - - contentStream.close(); - - // Now, set the new page's media box to the cropped size - newPage.setMediaBox(new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight())); -} - -ByteArrayOutputStream baos = new ByteArrayOutputStream(); -newDocument.save(baos); -newDocument.close(); -sourceDocument.close(); - -byte[] pdfContent = baos.toByteArray(); -return WebResponseUtils.bytesToWebResponse(pdfContent, form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf"); - } + contentStream.restoreGraphicsState(); + contentStream.close(); + + // Now, set the new page's media box to the cropped size + newPage.setMediaBox( + new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight())); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + newDocument.save(baos); + newDocument.close(); + sourceDocument.close(); + + byte[] pdfContent = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse( + pdfContent, + form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_cropped.pdf"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 7db00a31..70b79191 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -1,7 +1,15 @@ package stirling.software.SPDF.controller.api; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.multipdf.PDFMergerUtility; import org.apache.pdfbox.pdmodel.PDDocument; @@ -14,19 +22,13 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.MergePdfsRequest; import stirling.software.SPDF.utils.WebResponseUtils; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - @RestController @RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") @@ -34,7 +36,6 @@ public class MergeController { private static final Logger logger = LoggerFactory.getLogger(MergeController.class); - private PDDocument mergeDocuments(List documents) throws IOException { PDDocument mergedDoc = new PDDocument(); for (PDDocument doc : documents) { @@ -52,27 +53,39 @@ public class MergeController { case "byDateModified": return (file1, file2) -> { try { - BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class); - BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class); + BasicFileAttributes attr1 = + Files.readAttributes( + Paths.get(file1.getOriginalFilename()), + BasicFileAttributes.class); + BasicFileAttributes attr2 = + Files.readAttributes( + Paths.get(file2.getOriginalFilename()), + BasicFileAttributes.class); return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime()); } catch (IOException e) { - return 0; // If there's an error, treat them as equal + return 0; // If there's an error, treat them as equal } }; case "byDateCreated": return (file1, file2) -> { try { - BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class); - BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class); + BasicFileAttributes attr1 = + Files.readAttributes( + Paths.get(file1.getOriginalFilename()), + BasicFileAttributes.class); + BasicFileAttributes attr2 = + Files.readAttributes( + Paths.get(file2.getOriginalFilename()), + BasicFileAttributes.class); return attr1.creationTime().compareTo(attr2.creationTime()); } catch (IOException e) { - return 0; // If there's an error, treat them as equal + return 0; // If there's an error, treat them as equal } }; case "byPDFTitle": return (file1, file2) -> { try (PDDocument doc1 = PDDocument.load(file1.getInputStream()); - PDDocument doc2 = PDDocument.load(file2.getInputStream())) { + PDDocument doc2 = PDDocument.load(file2.getInputStream())) { String title1 = doc1.getDocumentInformation().getTitle(); String title2 = doc2.getDocumentInformation().getTitle(); return title1.compareTo(title2); @@ -82,14 +95,17 @@ public class MergeController { }; case "orderProvided": default: - return (file1, file2) -> 0; // Default is the order provided + return (file1, file2) -> 0; // Default is the order provided } } @PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs") - @Operation(summary = "Merge multiple PDF files into one", - description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO") - public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest form) throws IOException { + @Operation( + summary = "Merge multiple PDF files into one", + description = + "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO") + public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest form) + throws IOException { try { MultipartFile[] files = form.getFileInput(); Arrays.sort(files, getSortComparator(form.getSortType())); @@ -101,14 +117,16 @@ public class MergeController { mergedDoc.addSource(new ByteArrayInputStream(file.getBytes())); } - mergedDoc.setDestinationFileName(files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); + mergedDoc.setDestinationFileName( + files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); mergedDoc.setDestinationStream(docOutputstream); mergedDoc.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); - return WebResponseUtils.bytesToWebResponse(docOutputstream.toByteArray(), mergedDoc.getDestinationFileName()); + return WebResponseUtils.bytesToWebResponse( + docOutputstream.toByteArray(), mergedDoc.getDestinationFileName()); } catch (Exception ex) { logger.error("Error in merge pdf process", ex); - throw ex; + throw ex; } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java index ebe81ffd..52127571 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java @@ -1,6 +1,5 @@ package stirling.software.SPDF.controller.api; - import java.awt.Color; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -31,94 +31,110 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "General", description = "General APIs") public class MultiPageLayoutController { - private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class); + private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class); - @PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data") - @Operation( - summary = "Merge multiple pages of a PDF document into a single page", - description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity mergeMultiplePagesIntoOne(@ModelAttribute MergeMultiplePagesRequest request) - throws IOException { + @PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data") + @Operation( + summary = "Merge multiple pages of a PDF document into a single page", + description = + "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO") + public ResponseEntity mergeMultiplePagesIntoOne( + @ModelAttribute MergeMultiplePagesRequest request) throws IOException { - int pagesPerSheet = request.getPagesPerSheet(); - MultipartFile file = request.getFileInput(); - boolean addBorder = request.isAddBorder(); - - if (pagesPerSheet != 2 && pagesPerSheet != 3 && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) { - throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square"); - } + int pagesPerSheet = request.getPagesPerSheet(); + MultipartFile file = request.getFileInput(); + boolean addBorder = request.isAddBorder(); - int cols = pagesPerSheet == 2 || pagesPerSheet == 3 ? pagesPerSheet : (int) Math.sqrt(pagesPerSheet); - int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); + if (pagesPerSheet != 2 + && pagesPerSheet != 3 + && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) { + throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square"); + } - PDDocument sourceDocument = PDDocument.load(file.getInputStream()); - PDDocument newDocument = new PDDocument(); - PDPage newPage = new PDPage(PDRectangle.A4); - newDocument.addPage(newPage); + int cols = + pagesPerSheet == 2 || pagesPerSheet == 3 + ? pagesPerSheet + : (int) Math.sqrt(pagesPerSheet); + int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); - int totalPages = sourceDocument.getNumberOfPages(); - float cellWidth = newPage.getMediaBox().getWidth() / cols; - float cellHeight = newPage.getMediaBox().getHeight() / rows; + PDDocument sourceDocument = PDDocument.load(file.getInputStream()); + PDDocument newDocument = new PDDocument(); + PDPage newPage = new PDPage(PDRectangle.A4); + newDocument.addPage(newPage); - PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true); - LayerUtility layerUtility = new LayerUtility(newDocument); + int totalPages = sourceDocument.getNumberOfPages(); + float cellWidth = newPage.getMediaBox().getWidth() / cols; + float cellHeight = newPage.getMediaBox().getHeight() / rows; - float borderThickness = 1.5f; // Specify border thickness as required - contentStream.setLineWidth(borderThickness); - contentStream.setStrokingColor(Color.BLACK); - - for (int i = 0; i < totalPages; i++) { - if (i != 0 && i % pagesPerSheet == 0) { - // Close the current content stream and create a new page and content stream - contentStream.close(); - newPage = new PDPage(PDRectangle.A4); - newDocument.addPage(newPage); - contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true); - } + PDPageContentStream contentStream = + new PDPageContentStream( + newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true); + LayerUtility layerUtility = new LayerUtility(newDocument); - PDPage sourcePage = sourceDocument.getPage(i); - PDRectangle rect = sourcePage.getMediaBox(); - float scaleWidth = cellWidth / rect.getWidth(); - float scaleHeight = cellHeight / rect.getHeight(); - float scale = Math.min(scaleWidth, scaleHeight); + float borderThickness = 1.5f; // Specify border thickness as required + contentStream.setLineWidth(borderThickness); + contentStream.setStrokingColor(Color.BLACK); - int adjustedPageIndex = i % pagesPerSheet; // This will reset the index for every new page - int rowIndex = adjustedPageIndex / cols; - int colIndex = adjustedPageIndex % cols; + for (int i = 0; i < totalPages; i++) { + if (i != 0 && i % pagesPerSheet == 0) { + // Close the current content stream and create a new page and content stream + contentStream.close(); + newPage = new PDPage(PDRectangle.A4); + newDocument.addPage(newPage); + contentStream = + new PDPageContentStream( + newDocument, + newPage, + PDPageContentStream.AppendMode.APPEND, + true, + true); + } - float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2; - float y = newPage.getMediaBox().getHeight() - ((rowIndex + 1) * cellHeight - (cellHeight - rect.getHeight() * scale) / 2); + PDPage sourcePage = sourceDocument.getPage(i); + PDRectangle rect = sourcePage.getMediaBox(); + float scaleWidth = cellWidth / rect.getWidth(); + float scaleHeight = cellHeight / rect.getHeight(); + float scale = Math.min(scaleWidth, scaleHeight); - contentStream.saveGraphicsState(); - contentStream.transform(Matrix.getTranslateInstance(x, y)); - contentStream.transform(Matrix.getScaleInstance(scale, scale)); + int adjustedPageIndex = + i % pagesPerSheet; // This will reset the index for every new page + int rowIndex = adjustedPageIndex / cols; + int colIndex = adjustedPageIndex % cols; - PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); - contentStream.drawForm(formXObject); + float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2; + float y = + newPage.getMediaBox().getHeight() + - ((rowIndex + 1) * cellHeight + - (cellHeight - rect.getHeight() * scale) / 2); - contentStream.restoreGraphicsState(); - - if(addBorder) { - // Draw border around each page - float borderX = colIndex * cellWidth; - float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight; - contentStream.addRect(borderX, borderY, cellWidth, cellHeight); - contentStream.stroke(); - } - } + contentStream.saveGraphicsState(); + contentStream.transform(Matrix.getTranslateInstance(x, y)); + contentStream.transform(Matrix.getScaleInstance(scale, scale)); + PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); + contentStream.drawForm(formXObject); - contentStream.close(); // Close the final content stream - sourceDocument.close(); + contentStream.restoreGraphicsState(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - newDocument.save(baos); - newDocument.close(); + if (addBorder) { + // Draw border around each page + float borderX = colIndex * cellWidth; + float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight; + contentStream.addRect(borderX, borderY, cellWidth, cellHeight); + contentStream.stroke(); + } + } - byte[] result = baos.toByteArray(); - return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf"); - } + contentStream.close(); // Close the final content stream + sourceDocument.close(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + newDocument.save(baos); + newDocument.close(); + byte[] result = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse( + result, + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java index 9551754a..f6099c3a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java @@ -1,11 +1,13 @@ package stirling.software.SPDF.controller.api; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.ArrayList; + import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.pdmodel.PDDocument; import org.springframework.http.MediaType; @@ -18,36 +20,49 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.OverlayPdfsRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class PdfOverlayController { - @PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data") - @Operation(summary = "Overlay PDF files in various modes", description = "Overlay PDF files onto a base PDF with different modes: Sequential, Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO") - public ResponseEntity overlayPdfs(@ModelAttribute OverlayPdfsRequest request) throws IOException { + @PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data") + @Operation( + summary = "Overlay PDF files in various modes", + description = + "Overlay PDF files onto a base PDF with different modes: Sequential, Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO") + public ResponseEntity overlayPdfs(@ModelAttribute OverlayPdfsRequest request) + throws IOException { MultipartFile baseFile = request.getFileInput(); int overlayPos = request.getOverlayPosition(); - + MultipartFile[] overlayFiles = request.getOverlayFiles(); File[] overlayPdfFiles = new File[overlayFiles.length]; List tempFiles = new ArrayList<>(); // List to keep track of temporary files - try { + try { for (int i = 0; i < overlayFiles.length; i++) { overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]); } - - String mode = request.getOverlayMode(); // "SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay" + + String mode = request.getOverlayMode(); // "SequentialOverlay", "InterleavedOverlay", + // "FixedRepeatOverlay" int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream()); Overlay overlay = new Overlay()) { - Map overlayGuide = prepareOverlayGuide(basePdf.getNumberOfPages(), overlayPdfFiles, mode, counts, tempFiles); - + Map overlayGuide = + prepareOverlayGuide( + basePdf.getNumberOfPages(), + overlayPdfFiles, + mode, + counts, + tempFiles); + overlay.setInputPDF(basePdf); if (overlayPos == 0) { overlay.setOverlayPosition(Overlay.Position.FOREGROUND); @@ -58,10 +73,13 @@ public class PdfOverlayController { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); overlay.overlay(overlayGuide).save(outputStream); byte[] data = outputStream.toByteArray(); - String outputFilename = baseFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"; // Remove file extension and append .pdf - - return WebResponseUtils.bytesToWebResponse(data, outputFilename, MediaType.APPLICATION_PDF); - } + String outputFilename = + baseFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_overlayed.pdf"; // Remove file extension and append .pdf + + return WebResponseUtils.bytesToWebResponse( + data, outputFilename, MediaType.APPLICATION_PDF); + } } finally { for (File overlayPdfFile : overlayPdfFiles) { if (overlayPdfFile != null) { @@ -76,7 +94,9 @@ public class PdfOverlayController { } } - private Map prepareOverlayGuide(int basePageCount, File[] overlayFiles, String mode, int[] counts, List tempFiles) throws IOException { + private Map prepareOverlayGuide( + int basePageCount, File[] overlayFiles, String mode, int[] counts, List tempFiles) + throws IOException { Map overlayGuide = new HashMap<>(); switch (mode) { case "SequentialOverlay": @@ -94,12 +114,19 @@ public class PdfOverlayController { return overlayGuide; } - private void sequentialOverlay(Map overlayGuide, File[] overlayFiles, int basePageCount, List tempFiles) throws IOException { + private void sequentialOverlay( + Map overlayGuide, + File[] overlayFiles, + int basePageCount, + List tempFiles) + throws IOException { int overlayFileIndex = 0; int pageCountInCurrentOverlay = 0; for (int basePageIndex = 1; basePageIndex <= basePageCount; basePageIndex++) { - if (pageCountInCurrentOverlay == 0 || pageCountInCurrentOverlay >= getNumberOfPages(overlayFiles[overlayFileIndex])) { + if (pageCountInCurrentOverlay == 0 + || pageCountInCurrentOverlay + >= getNumberOfPages(overlayFiles[overlayFileIndex])) { pageCountInCurrentOverlay = 0; overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length; } @@ -125,13 +152,9 @@ public class PdfOverlayController { } } - - - - - - - private void interleavedOverlay(Map overlayGuide, File[] overlayFiles, int basePageCount) throws IOException { + private void interleavedOverlay( + Map overlayGuide, File[] overlayFiles, int basePageCount) + throws IOException { for (int basePageIndex = 1; basePageIndex <= basePageCount; basePageIndex++) { File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length]; @@ -145,10 +168,12 @@ public class PdfOverlayController { } } - - private void fixedRepeatOverlay(Map overlayGuide, File[] overlayFiles, int[] counts, int basePageCount) throws IOException { + private void fixedRepeatOverlay( + Map overlayGuide, File[] overlayFiles, int[] counts, int basePageCount) + throws IOException { if (overlayFiles.length != counts.length) { - throw new IllegalArgumentException("Counts array length must match the number of overlay files"); + throw new IllegalArgumentException( + "Counts array length must match the number of overlay files"); } int currentPage = 1; for (int i = 0; i < overlayFiles.length; i++) { @@ -167,7 +192,7 @@ public class PdfOverlayController { } } } - } -// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined elsewhere. +// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined +// elsewhere. diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index b20bd99b..8c31bf3c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -17,200 +17,204 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.SortTypes; import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.general.RearrangePagesRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class RearrangePagesPDFController { - private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); + private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); - @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") - @Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO") - public ResponseEntity deletePages(@ModelAttribute PDFWithPageNums request ) - throws IOException { + @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") + @Operation( + summary = "Remove pages from a PDF file", + description = + "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO") + public ResponseEntity deletePages(@ModelAttribute PDFWithPageNums request) + throws IOException { - MultipartFile pdfFile = request.getFileInput(); - String pagesToDelete = request.getPageNumbers(); - - PDDocument document = PDDocument.load(pdfFile.getBytes()); + MultipartFile pdfFile = request.getFileInput(); + String pagesToDelete = request.getPageNumbers(); - // Split the page order string into an array of page numbers or range of numbers - String[] pageOrderArr = pagesToDelete.split(","); + PDDocument document = PDDocument.load(pdfFile.getBytes()); - List pagesToRemove = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pagesToDelete.split(","); - for (int i = pagesToRemove.size() - 1; i >= 0; i--) { - int pageIndex = pagesToRemove.get(i); - document.removePage(pageIndex); - } - return WebResponseUtils.pdfDocToWebResponse(document, - pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); + List pagesToRemove = + GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); - } - - - - private List removeFirst(int totalPages) { - if (totalPages <= 1) - return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 2; i <= totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } - - private List removeLast(int totalPages) { - if (totalPages <= 1) - return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 1; i < totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } - - private List removeFirstAndLast(int totalPages) { - if (totalPages <= 2) - return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 2; i < totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } - - private List reverseOrder(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = totalPages; i >= 1; i--) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } - - private List duplexSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages - for (int i = 1; i <= half; i++) { - newPageOrder.add(i - 1); - if (i <= totalPages - half) { // Avoid going out of bounds - newPageOrder.add(totalPages - i); - } - } - return newPageOrder; - } - - private List bookletSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 0; i < totalPages / 2; i++) { - newPageOrder.add(i); - newPageOrder.add(totalPages - i - 1); - } - return newPageOrder; - } - - private List sideStitchBooklet(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 0; i < (totalPages + 3) / 4; i++) { - int begin = i * 4; - newPageOrder.add(Math.min(begin + 3, totalPages - 1)); - newPageOrder.add(Math.min(begin, totalPages - 1)); - newPageOrder.add(Math.min(begin + 1, totalPages - 1)); - newPageOrder.add(Math.min(begin + 2, totalPages - 1)); - } - return newPageOrder; + for (int i = pagesToRemove.size() - 1; i >= 0; i--) { + int pageIndex = pagesToRemove.get(i); + document.removePage(pageIndex); + } + return WebResponseUtils.pdfDocToWebResponse( + document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); } - private List oddEvenSplit(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 1; i <= totalPages; i += 2) { - newPageOrder.add(i - 1); - } - for (int i = 2; i <= totalPages; i += 2) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List removeFirst(int totalPages) { + if (totalPages <= 1) return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 2; i <= totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List processSortTypes(String sortTypes, int totalPages) { - try { - SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase()); - switch (mode) { - case REVERSE_ORDER: - return reverseOrder(totalPages); - case DUPLEX_SORT: - return duplexSort(totalPages); - case BOOKLET_SORT: - return bookletSort(totalPages); - case SIDE_STITCH_BOOKLET_SORT: - return sideStitchBooklet(totalPages); - case ODD_EVEN_SPLIT: - return oddEvenSplit(totalPages); - case REMOVE_FIRST: - return removeFirst(totalPages); - case REMOVE_LAST: - return removeLast(totalPages); - case REMOVE_FIRST_AND_LAST: - return removeFirstAndLast(totalPages); - default: - throw new IllegalArgumentException("Unsupported custom mode"); - } - } catch (IllegalArgumentException e) { - logger.error("Unsupported custom mode", e); - return null; - } - } + private List removeLast(int totalPages) { + if (totalPages <= 1) return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 1; i < totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") - @Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF") - public ResponseEntity rearrangePages(@ModelAttribute RearrangePagesRequest request) throws IOException { - MultipartFile pdfFile = request.getFileInput(); - String pageOrder = request.getPageNumbers(); - String sortType = request.getCustomMode(); - try { - // Load the input PDF - PDDocument document = PDDocument.load(pdfFile.getInputStream()); + private List removeFirstAndLast(int totalPages) { + if (totalPages <= 2) return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 2; i < totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - // Split the page order string into an array of page numbers or range of numbers - String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; - int totalPages = document.getNumberOfPages(); - List newPageOrder; - if (sortType != null && sortType.length() > 0) { - newPageOrder = processSortTypes(sortType, totalPages); - } else { - newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages); - } - logger.info("newPageOrder = " +newPageOrder); - logger.info("totalPages = " +totalPages); - // Create a new list to hold the pages in the new order - List newPages = new ArrayList<>(); - for (int i = 0; i < newPageOrder.size(); i++) { - newPages.add(document.getPage(newPageOrder.get(i))); - } + private List reverseOrder(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = totalPages; i >= 1; i--) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - // Remove all the pages from the original document - for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { - document.removePage(i); - } + private List duplexSort(int totalPages) { + List newPageOrder = new ArrayList<>(); + int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages + for (int i = 1; i <= half; i++) { + newPageOrder.add(i - 1); + if (i <= totalPages - half) { // Avoid going out of bounds + newPageOrder.add(totalPages - i); + } + } + return newPageOrder; + } - // Add the pages in the new order - for (PDPage page : newPages) { - document.addPage(page); - } + private List bookletSort(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 0; i < totalPages / 2; i++) { + newPageOrder.add(i); + newPageOrder.add(totalPages - i - 1); + } + return newPageOrder; + } - return WebResponseUtils.pdfDocToWebResponse(document, - pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); - } catch (IOException e) { - logger.error("Failed rearranging documents", e); - return null; - } - } + private List sideStitchBooklet(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 0; i < (totalPages + 3) / 4; i++) { + int begin = i * 4; + newPageOrder.add(Math.min(begin + 3, totalPages - 1)); + newPageOrder.add(Math.min(begin, totalPages - 1)); + newPageOrder.add(Math.min(begin + 1, totalPages - 1)); + newPageOrder.add(Math.min(begin + 2, totalPages - 1)); + } + return newPageOrder; + } - + private List oddEvenSplit(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 1; i <= totalPages; i += 2) { + newPageOrder.add(i - 1); + } + for (int i = 2; i <= totalPages; i += 2) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } + private List processSortTypes(String sortTypes, int totalPages) { + try { + SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase()); + switch (mode) { + case REVERSE_ORDER: + return reverseOrder(totalPages); + case DUPLEX_SORT: + return duplexSort(totalPages); + case BOOKLET_SORT: + return bookletSort(totalPages); + case SIDE_STITCH_BOOKLET_SORT: + return sideStitchBooklet(totalPages); + case ODD_EVEN_SPLIT: + return oddEvenSplit(totalPages); + case REMOVE_FIRST: + return removeFirst(totalPages); + case REMOVE_LAST: + return removeLast(totalPages); + case REMOVE_FIRST_AND_LAST: + return removeFirstAndLast(totalPages); + default: + throw new IllegalArgumentException("Unsupported custom mode"); + } + } catch (IllegalArgumentException e) { + logger.error("Unsupported custom mode", e); + return null; + } + } + + @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") + @Operation( + summary = "Rearrange pages in a PDF file", + description = + "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF") + public ResponseEntity rearrangePages(@ModelAttribute RearrangePagesRequest request) + throws IOException { + MultipartFile pdfFile = request.getFileInput(); + String pageOrder = request.getPageNumbers(); + String sortType = request.getCustomMode(); + try { + // Load the input PDF + PDDocument document = PDDocument.load(pdfFile.getInputStream()); + + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; + int totalPages = document.getNumberOfPages(); + List newPageOrder; + if (sortType != null && sortType.length() > 0) { + newPageOrder = processSortTypes(sortType, totalPages); + } else { + newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages); + } + logger.info("newPageOrder = " + newPageOrder); + logger.info("totalPages = " + totalPages); + // Create a new list to hold the pages in the new order + List newPages = new ArrayList<>(); + for (int i = 0; i < newPageOrder.size(); i++) { + newPages.add(document.getPage(newPageOrder.get(i))); + } + + // Remove all the pages from the original document + for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { + document.removePage(i); + } + + // Add the pages in the new order + for (PDPage page : newPages) { + document.addPage(page); + } + + return WebResponseUtils.pdfDocToWebResponse( + document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_rearranged.pdf"); + } catch (IOException e) { + logger.error("Failed rearranging documents", e); + return null; + } + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java index ed527549..883beb5d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java @@ -16,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.RotatePDFRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -28,11 +29,11 @@ public class RotationController { @PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf") @Operation( - summary = "Rotate a PDF file", - description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity rotatePDF( - @ModelAttribute RotatePDFRequest request) throws IOException { + summary = "Rotate a PDF file", + description = + "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO") + public ResponseEntity rotatePDF(@ModelAttribute RotatePDFRequest request) + throws IOException { MultipartFile pdfFile = request.getFileInput(); Integer angle = request.getAngle(); // Load the PDF document @@ -45,8 +46,8 @@ public class RotationController { page.setRotation(page.getRotation() + angle); } - return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf"); - + return WebResponseUtils.pdfDocToWebResponse( + document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf"); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 743ea6b1..0dcec05c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -23,88 +23,90 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.ScalePagesRequest; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class ScalePagesController { - private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class); + private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class); - @PostMapping(value = "/scale-pages", consumes = "multipart/form-data") - @Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO") - public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest request) throws IOException { - MultipartFile file = request.getFileInput(); - String targetPDRectangle = request.getPageSize(); - float scaleFactor = request.getScaleFactor(); + @PostMapping(value = "/scale-pages", consumes = "multipart/form-data") + @Operation( + summary = "Change the size of a PDF page/document", + description = + "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO") + public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest request) + throws IOException { + MultipartFile file = request.getFileInput(); + String targetPDRectangle = request.getPageSize(); + float scaleFactor = request.getScaleFactor(); - Map sizeMap = new HashMap<>(); - // Add A0 - A10 - sizeMap.put("A0", PDRectangle.A0); - sizeMap.put("A1", PDRectangle.A1); - sizeMap.put("A2", PDRectangle.A2); - sizeMap.put("A3", PDRectangle.A3); - sizeMap.put("A4", PDRectangle.A4); - sizeMap.put("A5", PDRectangle.A5); - sizeMap.put("A6", PDRectangle.A6); + Map sizeMap = new HashMap<>(); + // Add A0 - A10 + sizeMap.put("A0", PDRectangle.A0); + sizeMap.put("A1", PDRectangle.A1); + sizeMap.put("A2", PDRectangle.A2); + sizeMap.put("A3", PDRectangle.A3); + sizeMap.put("A4", PDRectangle.A4); + sizeMap.put("A5", PDRectangle.A5); + sizeMap.put("A6", PDRectangle.A6); - // Add other sizes - sizeMap.put("LETTER", PDRectangle.LETTER); - sizeMap.put("LEGAL", PDRectangle.LEGAL); + // Add other sizes + sizeMap.put("LETTER", PDRectangle.LETTER); + sizeMap.put("LEGAL", PDRectangle.LEGAL); - if (!sizeMap.containsKey(targetPDRectangle)) { - throw new IllegalArgumentException( - "Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); - } + if (!sizeMap.containsKey(targetPDRectangle)) { + throw new IllegalArgumentException( + "Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); + } - PDRectangle targetSize = sizeMap.get(targetPDRectangle); + PDRectangle targetSize = sizeMap.get(targetPDRectangle); - PDDocument sourceDocument = PDDocument.load(file.getBytes()); - PDDocument outputDocument = new PDDocument(); + PDDocument sourceDocument = PDDocument.load(file.getBytes()); + PDDocument outputDocument = new PDDocument(); - int totalPages = sourceDocument.getNumberOfPages(); - for (int i = 0; i < totalPages; i++) { - PDPage sourcePage = sourceDocument.getPage(i); - PDRectangle sourceSize = sourcePage.getMediaBox(); - - float scaleWidth = targetSize.getWidth() / sourceSize.getWidth(); - float scaleHeight = targetSize.getHeight() / sourceSize.getHeight(); - float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor; - - PDPage newPage = new PDPage(targetSize); - outputDocument.addPage(newPage); - - PDPageContentStream contentStream = new PDPageContentStream(outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true); - - float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2; - float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2; - - contentStream.saveGraphicsState(); - contentStream.transform(Matrix.getTranslateInstance(x, y)); - contentStream.transform(Matrix.getScaleInstance(scale, scale)); - - LayerUtility layerUtility = new LayerUtility(outputDocument); - PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i); - contentStream.drawForm(form); + int totalPages = sourceDocument.getNumberOfPages(); + for (int i = 0; i < totalPages; i++) { + PDPage sourcePage = sourceDocument.getPage(i); + PDRectangle sourceSize = sourcePage.getMediaBox(); - contentStream.restoreGraphicsState(); - contentStream.close(); - } + float scaleWidth = targetSize.getWidth() / sourceSize.getWidth(); + float scaleHeight = targetSize.getHeight() / sourceSize.getHeight(); + float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor; + PDPage newPage = new PDPage(targetSize); + outputDocument.addPage(newPage); + PDPageContentStream contentStream = + new PDPageContentStream( + outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true); + float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2; + float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2; + contentStream.saveGraphicsState(); + contentStream.transform(Matrix.getTranslateInstance(x, y)); + contentStream.transform(Matrix.getScaleInstance(scale, scale)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - outputDocument.save(baos); - outputDocument.close(); - sourceDocument.close(); - - - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), - file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); - } + LayerUtility layerUtility = new LayerUtility(outputDocument); + PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i); + contentStream.drawForm(form); + contentStream.restoreGraphicsState(); + contentStream.close(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputDocument.save(baos); + outputDocument.close(); + sourceDocument.close(); + + return WebResponseUtils.bytesToWebResponse( + baos.toByteArray(), + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 0651949e..a521769e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -25,6 +25,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.utils.WebResponseUtils; @@ -36,19 +37,24 @@ public class SplitPDFController { private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); @PostMapping(consumes = "multipart/form-data", value = "/split-pages") - @Operation(summary = "Split a PDF file into separate documents", - description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO") - public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) throws IOException { - MultipartFile file = request.getFileInput(); + @Operation( + summary = "Split a PDF file into separate documents", + description = + "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO") + public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) + throws IOException { + MultipartFile file = request.getFileInput(); String pages = request.getPageNumbers(); // open the pdf document InputStream inputStream = file.getInputStream(); PDDocument document = PDDocument.load(inputStream); List pageNumbers = request.getPageNumbersList(document); - if(!pageNumbers.contains(document.getNumberOfPages() - 1)) - pageNumbers.add(document.getNumberOfPages()- 1); - logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); + if (!pageNumbers.contains(document.getNumberOfPages() - 1)) + pageNumbers.add(document.getNumberOfPages() - 1); + logger.info( + "Splitting PDF into pages: {}", + pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); // split the document List splitDocumentsBoas = new ArrayList<>(); @@ -72,7 +78,6 @@ public class SplitPDFController { } } - // closing the original document document.close(); @@ -104,8 +109,7 @@ public class SplitPDFController { Files.delete(zipFile); // return the Resource in the response - return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); - + return WebResponseUtils.bytesToWebResponse( + data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); } - -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index f36f64da..5d96920f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.api; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.Files; @@ -25,17 +26,22 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class SplitPdfBySectionsController { - - @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") - @Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO") - public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception { + @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") + @Operation( + summary = "Split PDF pages into smaller sections", + description = + "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO") + public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) + throws Exception { List splitDocumentsBoas = new ArrayList<>(); MultipartFile file = request.getFileInput(); @@ -59,8 +65,6 @@ public class SplitPdfBySectionsController { String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); byte[] data; - - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { int pageNum = 1; for (int i = 0; i < splitDocumentsBoas.size(); i++) { @@ -82,10 +86,13 @@ public class SplitPdfBySectionsController { Files.delete(zipFile); } - return WebResponseUtils.bytesToWebResponse(data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse( + data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM); } - - public List splitPdfPages(PDDocument document, int horizontalDivisions, int verticalDivisions) throws IOException { + + public List splitPdfPages( + PDDocument document, int horizontalDivisions, int verticalDivisions) + throws IOException { List splitDocuments = new ArrayList<>(); for (PDPage originalPage : document.getPages()) { @@ -103,9 +110,12 @@ public class SplitPdfBySectionsController { PDPage subPage = new PDPage(new PDRectangle(subPageWidth, subPageHeight)); subDoc.addPage(subPage); - PDFormXObject form = layerUtility.importPageAsForm(document, document.getPages().indexOf(originalPage)); + PDFormXObject form = + layerUtility.importPageAsForm( + document, document.getPages().indexOf(originalPage)); - try (PDPageContentStream contentStream = new PDPageContentStream(subDoc, subPage)) { + try (PDPageContentStream contentStream = + new PDPageContentStream(subDoc, subPage)) { // Set clipping area and position float translateX = -subPageWidth * i; float translateY = height - subPageHeight * (verticalDivisions - j); @@ -127,9 +137,4 @@ public class SplitPdfBySectionsController { return splitDocuments; } - - - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java index 9b25e8be..28ac4673 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.api; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.Files; @@ -20,6 +21,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -29,22 +31,23 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "General", description = "General APIs") public class SplitPdfBySizeController { - @PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data") - @Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n" - + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO") - public ResponseEntity autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception { - List splitDocumentsBoas = new ArrayList(); - - - + @Operation( + summary = "Auto split PDF pages into separate documents based on size or count", + description = + "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n" + + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO") + public ResponseEntity autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) + throws Exception { + List splitDocumentsBoas = new ArrayList(); + MultipartFile file = request.getFileInput(); PDDocument sourceDocument = PDDocument.load(file.getInputStream()); - - //0 = size, 1 = page count, 2 = doc count + + // 0 = size, 1 = page count, 2 = doc count int type = request.getSplitType(); String value = request.getSplitValue(); - + if (type == 0) { // Split by size long maxBytes = GeneralUtils.convertSizeToBytes(value); long currentSize = 0; @@ -93,7 +96,7 @@ public class SplitPdfBySizeController { splitDocumentsBoas.add(currentDocToByteArray(currentDoc)); } } else if (type == 2) { // Split by doc count - int documentCount = Integer.parseInt(value); + int documentCount = Integer.parseInt(value); int totalPageCount = sourceDocument.getNumberOfPages(); int pagesPerDocument = totalPageCount / documentCount; int extraPages = totalPageCount % documentCount; @@ -114,9 +117,7 @@ public class SplitPdfBySizeController { } sourceDocument.close(); - - - + Path zipFile = Files.createTempFile("split_documents", ".zip"); String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); byte[] data; @@ -135,19 +136,18 @@ public class SplitPdfBySizeController { } catch (Exception e) { e.printStackTrace(); } finally { - data = Files.readAllBytes(zipFile); + data = Files.readAllBytes(zipFile); Files.delete(zipFile); } - return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse( + data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); } - + private ByteArrayOutputStream currentDocToByteArray(PDDocument document) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); document.save(baos); document.close(); return baos; } - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java index 22bf1d70..cd971b55 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java @@ -20,8 +20,10 @@ import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") @@ -29,58 +31,61 @@ public class ToSinglePageController { private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class); - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page") @Operation( - summary = "Convert a multi-page PDF into a single long page PDF", - description = "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity pdfToSinglePage(@ModelAttribute PDFFile request) throws IOException { + summary = "Convert a multi-page PDF into a single long page PDF", + description = + "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO") + public ResponseEntity pdfToSinglePage(@ModelAttribute PDFFile request) + throws IOException { - // Load the source document - PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream()); + // Load the source document + PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream()); - // Calculate total height and max width - float totalHeight = 0; - float maxWidth = 0; - for (PDPage page : sourceDocument.getPages()) { - PDRectangle pageSize = page.getMediaBox(); - totalHeight += pageSize.getHeight(); - maxWidth = Math.max(maxWidth, pageSize.getWidth()); - } + // Calculate total height and max width + float totalHeight = 0; + float maxWidth = 0; + for (PDPage page : sourceDocument.getPages()) { + PDRectangle pageSize = page.getMediaBox(); + totalHeight += pageSize.getHeight(); + maxWidth = Math.max(maxWidth, pageSize.getWidth()); + } - // Create new document and page with calculated dimensions - PDDocument newDocument = new PDDocument(); - PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight)); - newDocument.addPage(newPage); + // Create new document and page with calculated dimensions + PDDocument newDocument = new PDDocument(); + PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight)); + newDocument.addPage(newPage); - // Initialize the content stream of the new page - PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); - contentStream.close(); - - LayerUtility layerUtility = new LayerUtility(newDocument); - float yOffset = totalHeight; + // Initialize the content stream of the new page + PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); + contentStream.close(); - // For each page, copy its content to the new page at the correct offset - for (PDPage page : sourceDocument.getPages()) { - PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, sourceDocument.getPages().indexOf(page)); - AffineTransform af = AffineTransform.getTranslateInstance(0, yOffset - page.getMediaBox().getHeight()); - layerUtility.wrapInSaveRestore(newPage); - String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page); - layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName); - yOffset -= page.getMediaBox().getHeight(); - } + LayerUtility layerUtility = new LayerUtility(newDocument); + float yOffset = totalHeight; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - newDocument.save(baos); - newDocument.close(); - sourceDocument.close(); + // For each page, copy its content to the new page at the correct offset + for (PDPage page : sourceDocument.getPages()) { + PDFormXObject form = + layerUtility.importPageAsForm( + sourceDocument, sourceDocument.getPages().indexOf(page)); + AffineTransform af = + AffineTransform.getTranslateInstance( + 0, yOffset - page.getMediaBox().getHeight()); + layerUtility.wrapInSaveRestore(newPage); + String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page); + layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName); + yOffset -= page.getMediaBox().getHeight(); + } - byte[] result = baos.toByteArray(); - return WebResponseUtils.bytesToWebResponse(result, request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + newDocument.save(baos); + newDocument.close(); + sourceDocument.close(); - - - + byte[] result = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse( + result, + request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_singlePage.pdf"); } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index 01a50a3b..89e81c99 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -29,14 +29,14 @@ import stirling.software.SPDF.model.User; @Controller @RequestMapping("/api/v1/user") public class UserController { - - @Autowired - private UserService userService; - + + @Autowired private UserService userService; + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/register") - public String register(@RequestParam String username, @RequestParam String password, Model model) { - if(userService.usernameExists(username)) { + public String register( + @RequestParam String username, @RequestParam String password, Model model) { + if (userService.usernameExists(username)) { model.addAttribute("error", "Username already exists"); return "register"; } @@ -44,39 +44,41 @@ public class UserController { userService.saveUser(username, password); return "redirect:/login?registered=true"; } - + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/change-username-and-password") - public RedirectView changeUsernameAndPassword(Principal principal, - @RequestParam String currentPassword, - @RequestParam String newUsername, - @RequestParam String newPassword, - HttpServletRequest request, - HttpServletResponse response, - RedirectAttributes redirectAttributes) { - if (principal == null) { - return new RedirectView("/change-creds?messageType=notAuthenticated"); - } + public RedirectView changeUsernameAndPassword( + Principal principal, + @RequestParam String currentPassword, + @RequestParam String newUsername, + @RequestParam String newPassword, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + if (principal == null) { + return new RedirectView("/change-creds?messageType=notAuthenticated"); + } - Optional userOpt = userService.findByUsername(principal.getName()); + Optional userOpt = userService.findByUsername(principal.getName()); - if (userOpt == null || userOpt.isEmpty()) { - return new RedirectView("/change-creds?messageType=userNotFound"); - } + if (userOpt == null || userOpt.isEmpty()) { + return new RedirectView("/change-creds?messageType=userNotFound"); + } - User user = userOpt.get(); + User user = userOpt.get(); - if (!userService.isPasswordCorrect(user, currentPassword)) { - return new RedirectView("/change-creds?messageType=incorrectPassword"); - } - - if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { - return new RedirectView("/change-creds?messageType=usernameExists"); - } + if (!userService.isPasswordCorrect(user, currentPassword)) { + return new RedirectView("/change-creds?messageType=incorrectPassword"); + } + if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { + return new RedirectView("/change-creds?messageType=usernameExists"); + } userService.changePassword(user, newPassword); - if(newUsername != null && newUsername.length() > 0 && !user.getUsername().equals(newUsername)) { + if (newUsername != null + && newUsername.length() > 0 + && !user.getUsername().equals(newUsername)) { userService.changeUsername(user, newUsername); } userService.changeFirstUse(user, false); @@ -87,36 +89,36 @@ public class UserController { return new RedirectView("/login?messageType=credsUpdated"); } - @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/change-username") - public RedirectView changeUsername(Principal principal, - @RequestParam String currentPassword, - @RequestParam String newUsername, - HttpServletRequest request, - HttpServletResponse response, - RedirectAttributes redirectAttributes) { - if (principal == null) { - return new RedirectView("/account?messageType=notAuthenticated"); - } + public RedirectView changeUsername( + Principal principal, + @RequestParam String currentPassword, + @RequestParam String newUsername, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + if (principal == null) { + return new RedirectView("/account?messageType=notAuthenticated"); + } - Optional userOpt = userService.findByUsername(principal.getName()); + Optional userOpt = userService.findByUsername(principal.getName()); - if (userOpt == null || userOpt.isEmpty()) { - return new RedirectView("/account?messageType=userNotFound"); - } + if (userOpt == null || userOpt.isEmpty()) { + return new RedirectView("/account?messageType=userNotFound"); + } - User user = userOpt.get(); + User user = userOpt.get(); - if (!userService.isPasswordCorrect(user, currentPassword)) { - return new RedirectView("/account?messageType=incorrectPassword"); - } + if (!userService.isPasswordCorrect(user, currentPassword)) { + return new RedirectView("/account?messageType=incorrectPassword"); + } - if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { - return new RedirectView("/account?messageType=usernameExists"); - } + if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { + return new RedirectView("/account?messageType=usernameExists"); + } - if(newUsername != null && newUsername.length() > 0) { + if (newUsername != null && newUsername.length() > 0) { userService.changeUsername(user, newUsername); } @@ -125,30 +127,31 @@ public class UserController { return new RedirectView("/login?messageType=credsUpdated"); } - + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/change-password") - public RedirectView changePassword(Principal principal, - @RequestParam String currentPassword, - @RequestParam String newPassword, - HttpServletRequest request, - HttpServletResponse response, - RedirectAttributes redirectAttributes) { - if (principal == null) { - return new RedirectView("/account?messageType=notAuthenticated"); - } + public RedirectView changePassword( + Principal principal, + @RequestParam String currentPassword, + @RequestParam String newPassword, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + if (principal == null) { + return new RedirectView("/account?messageType=notAuthenticated"); + } - Optional userOpt = userService.findByUsername(principal.getName()); + Optional userOpt = userService.findByUsername(principal.getName()); - if (userOpt == null || userOpt.isEmpty()) { - return new RedirectView("/account?messageType=userNotFound"); - } + if (userOpt == null || userOpt.isEmpty()) { + return new RedirectView("/account?messageType=userNotFound"); + } - User user = userOpt.get(); + User user = userOpt.get(); - if (!userService.isPasswordCorrect(user, currentPassword)) { - return new RedirectView("/account?messageType=incorrectPassword"); - } + if (!userService.isPasswordCorrect(user, currentPassword)) { + return new RedirectView("/account?messageType=incorrectPassword"); + } userService.changePassword(user, newPassword); @@ -160,33 +163,37 @@ public class UserController { @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/updateUserSettings") - public String updateUserSettings(HttpServletRequest request, Principal principal) { - Map paramMap = request.getParameterMap(); - Map updates = new HashMap<>(); + public String updateUserSettings(HttpServletRequest request, Principal principal) { + Map paramMap = request.getParameterMap(); + Map updates = new HashMap<>(); - System.out.println("Received parameter map: " + paramMap); + System.out.println("Received parameter map: " + paramMap); - for (Map.Entry entry : paramMap.entrySet()) { - updates.put(entry.getKey(), entry.getValue()[0]); - } + for (Map.Entry entry : paramMap.entrySet()) { + updates.put(entry.getKey(), entry.getValue()[0]); + } - System.out.println("Processed updates: " + updates); + System.out.println("Processed updates: " + updates); - // Assuming you have a method in userService to update the settings for a user - userService.updateUserSettings(principal.getName(), updates); + // Assuming you have a method in userService to update the settings for a user + userService.updateUserSettings(principal.getName(), updates); - return "redirect:/account"; // Redirect to a page of your choice after updating - } + return "redirect:/account"; // Redirect to a page of your choice after updating + } @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/admin/saveUser") - public RedirectView saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String role, - @RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) { - - if(userService.usernameExists(username)) { - return new RedirectView("/addUsers?messageType=usernameExists"); - } - try { + public RedirectView saveUser( + @RequestParam String username, + @RequestParam String password, + @RequestParam String role, + @RequestParam(name = "forceChange", required = false, defaultValue = "false") + boolean forceChange) { + + if (userService.usernameExists(username)) { + return new RedirectView("/addUsers?messageType=usernameExists"); + } + try { // Validate the role Role roleEnum = Role.fromString(role); if (roleEnum == Role.INTERNAL_API_USER) { @@ -197,28 +204,27 @@ public class UserController { // If the role ID is not valid, redirect with an error message return new RedirectView("/addUsers?messageType=invalidRole"); } - + userService.saveUser(username, password, role, forceChange); - return new RedirectView("/addUsers"); // Redirect to account page after adding the user + return new RedirectView("/addUsers"); // Redirect to account page after adding the user } - @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/admin/deleteUser/{username}") - public String deleteUser(@PathVariable String username, Authentication authentication) { - - // Get the currently authenticated username + public String deleteUser(@PathVariable String username, Authentication authentication) { + + // Get the currently authenticated username String currentUsername = authentication.getName(); // Check if the provided username matches the current session's username if (currentUsername.equals(username)) { throw new IllegalArgumentException("Cannot delete currently logined in user."); } - - userService.deleteUser(username); + + userService.deleteUser(username); return "redirect:/addUsers"; } - + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/get-api-key") public ResponseEntity getApiKey(Principal principal) { @@ -247,6 +253,4 @@ public class UserController { } return ResponseEntity.ok(apiKey); } - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index 5839dd2d..bec09040 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -9,6 +9,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; @@ -18,35 +19,30 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RequestMapping("/api/v1/convert") public class ConvertHtmlToPDF { + @PostMapping(consumes = "multipart/form-data", value = "/html/pdf") + @Operation( + summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", + description = + "This endpoint takes an HTML or ZIP file input and converts it to a PDF format.") + public ResponseEntity HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception { + MultipartFile fileInput = request.getFileInput(); - @PostMapping(consumes = "multipart/form-data", value = "/html/pdf") - @Operation( - summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", - description = "This endpoint takes an HTML or ZIP file input and converts it to a PDF format." - ) - public ResponseEntity HtmlToPdf( - @ModelAttribute GeneralFile request) - throws Exception { - MultipartFile fileInput = request.getFileInput(); + if (fileInput == null) { + throw new IllegalArgumentException( + "Please provide an HTML or ZIP file for conversion."); + } - if (fileInput == null) { - throw new IllegalArgumentException("Please provide an HTML or ZIP file for conversion."); - } + String originalFilename = fileInput.getOriginalFilename(); + if (originalFilename == null + || (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) { + throw new IllegalArgumentException("File must be either .html or .zip format."); + } + byte[] pdfBytes = FileToPdf.convertHtmlToPdf(fileInput.getBytes(), originalFilename); - String originalFilename = fileInput.getOriginalFilename(); - if (originalFilename == null || (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) { - throw new IllegalArgumentException("File must be either .html or .zip format."); - }byte[] pdfBytes = FileToPdf.convertHtmlToPdf( fileInput.getBytes(), originalFilename); - - String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf - - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); - } - - - - - - + String outputFilename = + originalFilename.replaceFirst("[.][^.]+$", "") + + ".pdf"; // Remove file extension and append .pdf + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java index f0c60b69..a3ea2841 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java @@ -20,6 +20,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.converters.ConvertToImageRequest; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; import stirling.software.SPDF.utils.PdfUtils; @@ -33,15 +34,18 @@ public class ConvertImgPDFController { private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); @PostMapping(consumes = "multipart/form-data", value = "/pdf/img") - @Operation(summary = "Convert PDF to image(s)", - description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional") - public ResponseEntity convertToImage(@ModelAttribute ConvertToImageRequest request) throws IOException { + @Operation( + summary = "Convert PDF to image(s)", + description = + "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional") + public ResponseEntity convertToImage(@ModelAttribute ConvertToImageRequest request) + throws IOException { MultipartFile file = request.getFileInput(); String imageFormat = request.getImageFormat(); String singleOrMultiple = request.getSingleOrMultiple(); String colorType = request.getColorType(); String dpi = request.getDpi(); - + byte[] pdfBytes = file.getBytes(); ImageType colorTypeResult = ImageType.RGB; if ("greyscale".equals(colorType)) { @@ -54,7 +58,14 @@ public class ConvertImgPDFController { byte[] result = null; String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); try { - result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi), filename); + result = + PdfUtils.convertFromPdf( + pdfBytes, + imageFormat.toUpperCase(), + colorTypeResult, + singleImage, + Integer.valueOf(dpi), + filename); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -65,29 +76,39 @@ public class ConvertImgPDFController { if (singleImage) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat))); - ResponseEntity response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK); + ResponseEntity response = + new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK); return response; } else { ByteArrayResource resource = new ByteArrayResource(result); // return the Resource in the response return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename + "_convertedToImages.zip") - .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); + .header( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=" + filename + "_convertedToImages.zip") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentLength(resource.contentLength()) + .body(resource); } } @PostMapping(consumes = "multipart/form-data", value = "/img/pdf") - @Operation(summary = "Convert images to a PDF file", - description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?") - public ResponseEntity convertToPdf(@ModelAttribute ConvertToPdfRequest request) throws IOException { + @Operation( + summary = "Convert images to a PDF file", + description = + "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?") + public ResponseEntity convertToPdf(@ModelAttribute ConvertToPdfRequest request) + throws IOException { MultipartFile[] file = request.getFileInput(); String fitOption = request.getFitOption(); String colorType = request.getColorType(); boolean autoRotate = request.isAutoRotate(); - + // Convert the file to PDF and get the resulting bytes byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType); - return WebResponseUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf"); + return WebResponseUtils.bytesToWebResponse( + bytes, + file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf"); } private String getMediaType(String imageFormat) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java index 4191ecdf..8bdc5049 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -12,6 +12,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; @@ -20,17 +21,16 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Convert", description = "Convert APIs") @RequestMapping("/api/v1/convert") public class ConvertMarkdownToPdf { - - @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") + + @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @Operation( - summary = "Convert a Markdown file to PDF", - description = "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format." - ) - public ResponseEntity markdownToPdf( - @ModelAttribute GeneralFile request) - throws Exception { - MultipartFile fileInput = request.getFileInput(); - + summary = "Convert a Markdown file to PDF", + description = + "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format.") + public ResponseEntity markdownToPdf(@ModelAttribute GeneralFile request) + throws Exception { + MultipartFile fileInput = request.getFileInput(); + if (fileInput == null) { throw new IllegalArgumentException("Please provide a Markdown file for conversion."); } @@ -45,10 +45,12 @@ public class ConvertMarkdownToPdf { Node document = parser.parse(new String(fileInput.getBytes())); HtmlRenderer renderer = HtmlRenderer.builder().build(); String htmlContent = renderer.render(document); - - byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html"); - String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf + byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html"); + + String outputFilename = + originalFilename.replaceFirst("[.][^.]+$", "") + + ".pdf"; // Remove file extension and append .pdf return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index e1c18a49..c0008046 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -18,6 +18,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -31,20 +32,33 @@ public class ConvertOfficeController { public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { // Check for valid file extension String originalFilename = inputFile.getOriginalFilename(); - if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { + if (originalFilename == null + || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { throw new IllegalArgumentException("Invalid file extension"); } // Save the uploaded file to a temporary location - Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); + Path tempInputFile = + Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); // Run the LibreOffice command - List command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString())); - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); + List command = + new ArrayList<>( + Arrays.asList( + "unoconv", + "-vvv", + "-f", + "pdf", + "-o", + tempOutputFile.toString(), + tempInputFile.toString())); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) + .runCommandWithOutputHandling(command); // Read the converted PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); @@ -55,6 +69,7 @@ public class ConvertOfficeController { return pdfBytes; } + private boolean isValidFileExtension(String fileExtension) { String extensionPattern = "^(?i)[a-z0-9]{2,4}$"; return fileExtension.matches(extensionPattern); @@ -62,17 +77,19 @@ public class ConvertOfficeController { @PostMapping(consumes = "multipart/form-data", value = "/file/pdf") @Operation( - summary = "Convert a file to a PDF using LibreOffice", - description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO" - ) - public ResponseEntity processFileToPDF(@ModelAttribute GeneralFile request) - throws Exception { - MultipartFile inputFile = request.getFileInput(); + summary = "Convert a file to a PDF using LibreOffice", + description = + "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO") + public ResponseEntity processFileToPDF(@ModelAttribute GeneralFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); // unused but can start server instance if startup time is to long // LibreOfficeListener.getInstance().start(); byte[] pdfByteArray = convertToPdf(inputFile); - return WebResponseUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf"); + return WebResponseUtils.bytesToWebResponse( + pdfByteArray, + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_convertedToPDF.pdf"); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java index 11279a27..74b292b5 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java @@ -11,6 +11,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest; import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest; @@ -22,51 +23,70 @@ import stirling.software.SPDF.utils.PDFToFile; @Tag(name = "Convert", description = "Convert APIs") public class ConvertPDFToOffice { - @PostMapping(consumes = "multipart/form-data", value = "/pdf/html") - @Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO") - public ResponseEntity processPdfToHTML(@ModelAttribute PDFFile request) - throws Exception { - MultipartFile inputFile = request.getFileInput(); - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import"); - } + @PostMapping(consumes = "multipart/form-data", value = "/pdf/html") + @Operation( + summary = "Convert PDF to HTML", + description = + "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO") + public ResponseEntity processPdfToHTML(@ModelAttribute PDFFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import"); + } - @PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation") - @Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO") - public ResponseEntity processPdfToPresentation(@ModelAttribute PdfToPresentationRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String outputFormat = request.getOutputFormat(); - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); - } + @PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation") + @Operation( + summary = "Convert PDF to Presentation format", + description = + "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO") + public ResponseEntity processPdfToPresentation( + @ModelAttribute PdfToPresentationRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String outputFormat = request.getOutputFormat(); + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); + } - @PostMapping(consumes = "multipart/form-data", value = "/pdf/text") - @Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO") - public ResponseEntity processPdfToRTForTXT(@ModelAttribute PdfToTextOrRTFRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String outputFormat = request.getOutputFormat(); + @PostMapping(consumes = "multipart/form-data", value = "/pdf/text") + @Operation( + summary = "Convert PDF to Text or RTF format", + description = + "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO") + public ResponseEntity processPdfToRTForTXT( + @ModelAttribute PdfToTextOrRTFRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String outputFormat = request.getOutputFormat(); - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); - } + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); + } - @PostMapping(consumes = "multipart/form-data", value = "/pdf/word") - @Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO") - public ResponseEntity processPdfToWord(@ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String outputFormat = request.getOutputFormat(); - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); - } + @PostMapping(consumes = "multipart/form-data", value = "/pdf/word") + @Operation( + summary = "Convert PDF to Word document", + description = + "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO") + public ResponseEntity processPdfToWord(@ModelAttribute PdfToWordRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String outputFormat = request.getOutputFormat(); + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); + } - @PostMapping(consumes = "multipart/form-data", value = "/pdf/xml") - @Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO") - public ResponseEntity processPdfToXML(@ModelAttribute PDFFile request) - throws Exception { - MultipartFile inputFile = request.getFileInput(); - - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); - } + @PostMapping(consumes = "multipart/form-data", value = "/pdf/xml") + @Operation( + summary = "Convert PDF to XML", + description = + "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO") + public ResponseEntity processPdfToXML(@ModelAttribute PDFFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 32ccb84a..ac8ce031 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -24,14 +25,13 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Convert", description = "Convert APIs") public class ConvertPDFToPDFA { - @PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa") - @Operation( - summary = "Convert a PDF to a PDF/A", - description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity pdfToPdfA(@ModelAttribute PDFFile request) - throws Exception { - MultipartFile inputFile = request.getFileInput(); + @PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa") + @Operation( + summary = "Convert a PDF to a PDF/A", + description = + "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO") + public ResponseEntity pdfToPdfA(@ModelAttribute PDFFile request) throws Exception { + MultipartFile inputFile = request.getFileInput(); // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); @@ -50,7 +50,9 @@ public class ConvertPDFToPDFA { command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) + .runCommandWithOutputHandling(command); // Read the optimized PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); @@ -60,8 +62,8 @@ public class ConvertPDFToPDFA { Files.delete(tempOutputFile); // Return the optimized PDF as a response - String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf"; + String outputFilename = + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf"; return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index d3f5c307..bf631c87 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; @@ -25,52 +26,52 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RequestMapping("/api/v1/convert") public class ConvertWebsiteToPDF { - @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") - @Operation( - summary = "Convert a URL to a PDF", - description = "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO" - ) - public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) throws IOException, InterruptedException { - String URL = request.getUrlInput(); + @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") + @Operation( + summary = "Convert a URL to a PDF", + description = + "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO") + public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) + throws IOException, InterruptedException { + String URL = request.getUrlInput(); - // Validate the URL format - if(!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { - throw new IllegalArgumentException("Invalid URL format provided."); - } - Path tempOutputFile = null; - byte[] pdfBytes; - try { - // Prepare the output file path - tempOutputFile = Files.createTempFile("output_", ".pdf"); - - // Prepare the OCRmyPDF command - List command = new ArrayList<>(); - command.add("weasyprint"); - command.add(URL); - command.add(tempOutputFile.toString()); - - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT).runCommandWithOutputHandling(command); - - // Read the optimized PDF file - pdfBytes = Files.readAllBytes(tempOutputFile); - } - finally { - // Clean up the temporary files - Files.delete(tempOutputFile); - } - // Convert URL to a safe filename - String outputFilename = convertURLToFileName(URL); - - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); - } + // Validate the URL format + if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { + throw new IllegalArgumentException("Invalid URL format provided."); + } + Path tempOutputFile = null; + byte[] pdfBytes; + try { + // Prepare the output file path + tempOutputFile = Files.createTempFile("output_", ".pdf"); - private String convertURLToFileName(String url) { - String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); - if(safeName.length() > 50) { - safeName = safeName.substring(0, 50); // restrict to 50 characters - } - return safeName + ".pdf"; - } + // Prepare the OCRmyPDF command + List command = new ArrayList<>(); + command.add("weasyprint"); + command.add(URL); + command.add(tempOutputFile.toString()); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling(command); + // Read the optimized PDF file + pdfBytes = Files.readAllBytes(tempOutputFile); + } finally { + // Clean up the temporary files + Files.delete(tempOutputFile); + } + // Convert URL to a safe filename + String outputFilename = convertURLToFileName(URL); + + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } + + private String convertURLToFileName(String url) { + String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); + if (safeName.length() > 50) { + safeName = safeName.substring(0, 50); // restrict to 50 characters + } + return safeName + ".pdf"; + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java index 6398e8b9..c5b9ea8d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java @@ -22,6 +22,7 @@ import com.opencsv.CSVWriter; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.controller.api.CropController; import stirling.software.SPDF.controller.api.strippers.PDFTableStripper; import stirling.software.SPDF.model.api.extract.PDFFilePage; @@ -34,21 +35,24 @@ public class ExtractController { private static final Logger logger = LoggerFactory.getLogger(CropController.class); @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") - @Operation(summary = "Extracts a PDF document to csv", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") - public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form) - throws Exception { + @Operation( + summary = "Extracts a PDF document to csv", + description = + "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") + public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception { ArrayList tableData = new ArrayList<>(); int columnsCount = 0; - try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) { + try (PDDocument document = + PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) { final double res = 72; // PDF units are at 72 DPI PDFTableStripper stripper = new PDFTableStripper(); PDPage pdPage = document.getPage(form.getPageId() - 1); stripper.extractTable(pdPage); columnsCount = stripper.getColumns(); for (int c = 0; c < columnsCount; ++c) { - for(int r=0; r notEmptyColumns = new ArrayList<>(); - for (String item: tableData) { - if(!item.trim().isEmpty()){ + for (String item : tableData) { + if (!item.trim().isEmpty()) { notEmptyColumns.add(item); - }else{ + } else { columnsCount--; } } - List fullTable = notEmptyColumns.stream().map((entity)-> - entity.replace('\n',' ').replace('\r',' ').trim().replaceAll("\\s{2,}", "|")).toList(); + List fullTable = + notEmptyColumns.stream() + .map( + (entity) -> + entity.replace('\n', ' ') + .replace('\r', ' ') + .trim() + .replaceAll("\\s{2,}", "|")) + .toList(); int rowsCount = fullTable.get(0).split("\\|").length; - ArrayList headersList = getTableHeaders(columnsCount,fullTable); - ArrayList recordList = getRecordsList(rowsCount,fullTable); - - if(headersList.size() == 0 && recordList.size() == 0) { - throw new Exception("No table detected, no headers or records found"); + ArrayList headersList = getTableHeaders(columnsCount, fullTable); + ArrayList recordList = getRecordsList(rowsCount, fullTable); + + if (headersList.size() == 0 && recordList.size() == 0) { + throw new Exception("No table detected, no headers or records found"); } - + StringWriter writer = new StringWriter(); try (CSVWriter csvWriter = new CSVWriter(writer)) { csvWriter.writeNext(headersList.toArray(new String[0])); @@ -85,35 +96,41 @@ public class ExtractController { } HttpHeaders headers = new HttpHeaders(); - headers.setContentDisposition(ContentDisposition.builder("attachment").filename(form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted.csv").build()); + headers.setContentDisposition( + ContentDisposition.builder("attachment") + .filename( + form.getFileInput() + .getOriginalFilename() + .replaceFirst("[.][^.]+$", "") + + "_extracted.csv") + .build()); headers.setContentType(MediaType.parseMediaType("text/csv")); - return ResponseEntity.ok() - .headers(headers) - .body(writer.toString()); + return ResponseEntity.ok().headers(headers).body(writer.toString()); } - private ArrayList getRecordsList( int rowsCounts ,List items){ + private ArrayList getRecordsList(int rowsCounts, List items) { ArrayList recordsList = new ArrayList<>(); - for (int b=1; b getTableHeaders(int columnsCount, List items){ + + private ArrayList getTableHeaders(int columnsCount, List items) { ArrayList resultList = new ArrayList<>(); - for (int i=0;i containsText(@ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String text = request.getText(); - String pageNumber = request.getPageNumbers(); - - PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); - if (PdfUtils.hasText(pdfDocument, pageNumber, text)) - return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename()); - return null; - } + @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") + @Operation( + summary = "Checks if a PDF contains set text, returns true if does", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity containsText(@ModelAttribute ContainsTextRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String text = request.getText(); + String pageNumber = request.getPageNumbers(); - // TODO - @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") - @Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity containsImage(@ModelAttribute PDFWithPageNums request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String pageNumber = request.getPageNumbers(); - - PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); - if (PdfUtils.hasImages(pdfDocument, pageNumber)) - return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename()); - return null; - } + PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); + if (PdfUtils.hasText(pdfDocument, pageNumber, text)) + return WebResponseUtils.pdfDocToWebResponse( + pdfDocument, inputFile.getOriginalFilename()); + return null; + } - @PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") - @Operation(summary = "Checks if a PDF is greater, less or equal to a setPageCount", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageCount(@ModelAttribute PDFComparisonAndCount request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String pageCount = request.getPageCount(); - String comparator = request.getComparator(); - // Load the PDF - PDDocument document = PDDocument.load(inputFile.getInputStream()); - int actualPageCount = document.getNumberOfPages(); + // TODO + @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") + @Operation( + summary = "Checks if a PDF contains an image", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity containsImage(@ModelAttribute PDFWithPageNums request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String pageNumber = request.getPageNumbers(); - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualPageCount > Integer.parseInt(pageCount); - break; - case "Equal": - valid = actualPageCount == Integer.parseInt(pageCount); - break; - case "Less": - valid = actualPageCount < Integer.parseInt(pageCount); - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } + PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); + if (PdfUtils.hasImages(pdfDocument, pageNumber)) + return WebResponseUtils.pdfDocToWebResponse( + pdfDocument, inputFile.getOriginalFilename()); + return null; + } - if (valid) - return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } + @PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") + @Operation( + summary = "Checks if a PDF is greater, less or equal to a setPageCount", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity pageCount(@ModelAttribute PDFComparisonAndCount request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String pageCount = request.getPageCount(); + String comparator = request.getComparator(); + // Load the PDF + PDDocument document = PDDocument.load(inputFile.getInputStream()); + int actualPageCount = document.getNumberOfPages(); - @PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") - @Operation(summary = "Checks if a PDF is of a certain size", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageSize(@ModelAttribute PageSizeRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String standardPageSize = request.getStandardPageSize(); - String comparator = request.getComparator(); + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualPageCount > Integer.parseInt(pageCount); + break; + case "Equal": + valid = actualPageCount == Integer.parseInt(pageCount); + break; + case "Less": + valid = actualPageCount < Integer.parseInt(pageCount); + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } - // Load the PDF - PDDocument document = PDDocument.load(inputFile.getInputStream()); + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } - PDPage firstPage = document.getPage(0); - PDRectangle actualPageSize = firstPage.getMediaBox(); + @PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") + @Operation( + summary = "Checks if a PDF is of a certain size", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity pageSize(@ModelAttribute PageSizeRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String standardPageSize = request.getStandardPageSize(); + String comparator = request.getComparator(); - // Calculate the area of the actual page size - float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); + // Load the PDF + PDDocument document = PDDocument.load(inputFile.getInputStream()); - // Get the standard size and calculate its area - PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); - float standardArea = standardSize.getWidth() * standardSize.getHeight(); + PDPage firstPage = document.getPage(0); + PDRectangle actualPageSize = firstPage.getMediaBox(); - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualArea > standardArea; - break; - case "Equal": - valid = actualArea == standardArea; - break; - case "Less": - valid = actualArea < standardArea; - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } + // Calculate the area of the actual page size + float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); - if (valid) - return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } + // Get the standard size and calculate its area + PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); + float standardArea = standardSize.getWidth() * standardSize.getHeight(); - @PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") - @Operation(summary = "Checks if a PDF is a set file size", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity fileSize(@ModelAttribute FileSizeRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String fileSize = request.getFileSize(); - String comparator = request.getComparator(); + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualArea > standardArea; + break; + case "Equal": + valid = actualArea == standardArea; + break; + case "Less": + valid = actualArea < standardArea; + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } - // Get the file size - long actualFileSize = inputFile.getSize(); + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualFileSize > Long.parseLong(fileSize); - break; - case "Equal": - valid = actualFileSize == Long.parseLong(fileSize); - break; - case "Less": - valid = actualFileSize < Long.parseLong(fileSize); - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } + @PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") + @Operation( + summary = "Checks if a PDF is a set file size", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity fileSize(@ModelAttribute FileSizeRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String fileSize = request.getFileSize(); + String comparator = request.getComparator(); - if (valid) - return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } + // Get the file size + long actualFileSize = inputFile.getSize(); - @PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") - @Operation(summary = "Checks if a PDF is of a certain rotation", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageRotation(@ModelAttribute PageRotationRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - int rotation = request.getRotation(); - String comparator = request.getComparator(); + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualFileSize > Long.parseLong(fileSize); + break; + case "Equal": + valid = actualFileSize == Long.parseLong(fileSize); + break; + case "Less": + valid = actualFileSize < Long.parseLong(fileSize); + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } - // Load the PDF - PDDocument document = PDDocument.load(inputFile.getInputStream()); + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } - // Get the rotation of the first page - PDPage firstPage = document.getPage(0); - int actualRotation = firstPage.getRotation(); - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualRotation > rotation; - break; - case "Equal": - valid = actualRotation == rotation; - break; - case "Less": - valid = actualRotation < rotation; - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } + @PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") + @Operation( + summary = "Checks if a PDF is of a certain rotation", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity pageRotation(@ModelAttribute PageRotationRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + int rotation = request.getRotation(); + String comparator = request.getComparator(); - if (valid) - return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; + // Load the PDF + PDDocument document = PDDocument.load(inputFile.getInputStream()); - } + // Get the rotation of the first page + PDPage firstPage = document.getPage(0); + int actualRotation = firstPage.getRotation(); + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualRotation > rotation; + break; + case "Equal": + valid = actualRotation == rotation; + break; + case "Less": + valid = actualRotation < rotation; + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java index fe8337d2..e81ef1e1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java @@ -19,8 +19,10 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/misc") @Tag(name = "Misc", description = "Miscellaneous APIs") @@ -32,97 +34,105 @@ public class AutoRenameController { private static final int LINE_LIMIT = 11; @PostMapping(consumes = "multipart/form-data", value = "/auto-rename") - @Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO") - public ResponseEntity extractHeader(@ModelAttribute ExtractHeaderRequest request) throws Exception { + @Operation( + summary = "Extract header from PDF file", + description = + "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO") + public ResponseEntity extractHeader(@ModelAttribute ExtractHeaderRequest request) + throws Exception { MultipartFile file = request.getFileInput(); Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback(); - PDDocument document = PDDocument.load(file.getInputStream()); - PDFTextStripper reader = new PDFTextStripper() { - class LineInfo { - String text; - float fontSize; + PDDocument document = PDDocument.load(file.getInputStream()); + PDFTextStripper reader = + new PDFTextStripper() { + class LineInfo { + String text; + float fontSize; - LineInfo(String text, float fontSize) { - this.text = text; - this.fontSize = fontSize; - } - } + LineInfo(String text, float fontSize) { + this.text = text; + this.fontSize = fontSize; + } + } - List lineInfos = new ArrayList<>(); - StringBuilder lineBuilder = new StringBuilder(); - float lastY = -1; - float maxFontSizeInLine = 0.0f; - int lineCount = 0; + List lineInfos = new ArrayList<>(); + StringBuilder lineBuilder = new StringBuilder(); + float lastY = -1; + float maxFontSizeInLine = 0.0f; + int lineCount = 0; - @Override - protected void processTextPosition(TextPosition text) { - if (lastY != text.getY() && lineCount < LINE_LIMIT) { - processLine(); - lineBuilder = new StringBuilder(text.getUnicode()); - maxFontSizeInLine = text.getFontSizeInPt(); - lastY = text.getY(); - lineCount++; - } else if (lineCount < LINE_LIMIT) { - lineBuilder.append(text.getUnicode()); - if (text.getFontSizeInPt() > maxFontSizeInLine) { - maxFontSizeInLine = text.getFontSizeInPt(); - } - } - } + @Override + protected void processTextPosition(TextPosition text) { + if (lastY != text.getY() && lineCount < LINE_LIMIT) { + processLine(); + lineBuilder = new StringBuilder(text.getUnicode()); + maxFontSizeInLine = text.getFontSizeInPt(); + lastY = text.getY(); + lineCount++; + } else if (lineCount < LINE_LIMIT) { + lineBuilder.append(text.getUnicode()); + if (text.getFontSizeInPt() > maxFontSizeInLine) { + maxFontSizeInLine = text.getFontSizeInPt(); + } + } + } - private void processLine() { - if (lineBuilder.length() > 0 && lineCount < LINE_LIMIT) { - lineInfos.add(new LineInfo(lineBuilder.toString(), maxFontSizeInLine)); - } - } + private void processLine() { + if (lineBuilder.length() > 0 && lineCount < LINE_LIMIT) { + lineInfos.add(new LineInfo(lineBuilder.toString(), maxFontSizeInLine)); + } + } - @Override - public String getText(PDDocument doc) throws IOException { - this.lineInfos.clear(); - this.lineBuilder = new StringBuilder(); - this.lastY = -1; - this.maxFontSizeInLine = 0.0f; - this.lineCount = 0; - super.getText(doc); - processLine(); // Process the last line + @Override + public String getText(PDDocument doc) throws IOException { + this.lineInfos.clear(); + this.lineBuilder = new StringBuilder(); + this.lastY = -1; + this.maxFontSizeInLine = 0.0f; + this.lineCount = 0; + super.getText(doc); + processLine(); // Process the last line - // Merge lines with same font size - List mergedLineInfos = new ArrayList<>(); - for (int i = 0; i < lineInfos.size(); i++) { - String mergedText = lineInfos.get(i).text; - float fontSize = lineInfos.get(i).fontSize; - while (i + 1 < lineInfos.size() && lineInfos.get(i + 1).fontSize == fontSize) { - mergedText += " " + lineInfos.get(i + 1).text; - i++; - } - mergedLineInfos.add(new LineInfo(mergedText, fontSize)); - } + // Merge lines with same font size + List mergedLineInfos = new ArrayList<>(); + for (int i = 0; i < lineInfos.size(); i++) { + String mergedText = lineInfos.get(i).text; + float fontSize = lineInfos.get(i).fontSize; + while (i + 1 < lineInfos.size() + && lineInfos.get(i + 1).fontSize == fontSize) { + mergedText += " " + lineInfos.get(i + 1).text; + i++; + } + mergedLineInfos.add(new LineInfo(mergedText, fontSize)); + } - // Sort lines by font size in descending order and get the first one - mergedLineInfos.sort(Comparator.comparing((LineInfo li) -> li.fontSize).reversed()); - String title = mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(0).text; + // Sort lines by font size in descending order and get the first one + mergedLineInfos.sort( + Comparator.comparing((LineInfo li) -> li.fontSize).reversed()); + String title = + mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(0).text; - return title != null ? title : (useFirstTextAsFallback ? (mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(mergedLineInfos.size() - 1).text) : null); - } + return title != null + ? title + : (useFirstTextAsFallback + ? (mergedLineInfos.isEmpty() + ? null + : mergedLineInfos.get(mergedLineInfos.size() - 1) + .text) + : null); + } + }; - }; + String header = reader.getText(document); - String header = reader.getText(document); - - - // Sanitize the header string by removing characters not allowed in a filename. if (header != null && header.length() < 255) { header = header.replaceAll("[/\\\\?%*:|\"<>]", ""); return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf"); } else { - logger.info("File has no good title to be found"); - return WebResponseUtils.pdfDocToWebResponse(document, file.getOriginalFilename()); + logger.info("File has no good title to be found"); + return WebResponseUtils.pdfDocToWebResponse(document, file.getOriginalFilename()); } } - - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index e62fc35f..e32e325e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.api.misc; + import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; @@ -32,6 +33,7 @@ import com.google.zxing.common.HybridBinarizer; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -43,8 +45,12 @@ public class AutoSplitPdfController { private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF"; @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") - @Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP-PDF Type:SISO") - public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException { + @Operation( + summary = "Auto split PDF pages into separate documents", + description = + "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP-PDF Type:SISO") + public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) + throws IOException { MultipartFile file = request.getFileInput(); boolean duplexMode = request.isDuplexMode(); @@ -107,29 +113,48 @@ public class AutoSplitPdfController { } catch (Exception e) { e.printStackTrace(); } finally { - data = Files.readAllBytes(zipFile); + data = Files.readAllBytes(zipFile); Files.delete(zipFile); } - return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse( + data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); } - private static String decodeQRCode(BufferedImage bufferedImage) { LuminanceSource source; if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte) { byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData(); - source = new PlanarYUVLuminanceSource(pixels, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), false); + source = + new PlanarYUVLuminanceSource( + pixels, + bufferedImage.getWidth(), + bufferedImage.getHeight(), + 0, + 0, + bufferedImage.getWidth(), + bufferedImage.getHeight(), + false); } else if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferInt) { int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData(); byte[] newPixels = new byte[pixels.length]; for (int i = 0; i < pixels.length; i++) { newPixels[i] = (byte) (pixels[i] & 0xff); } - source = new PlanarYUVLuminanceSource(newPixels, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), false); + source = + new PlanarYUVLuminanceSource( + newPixels, + bufferedImage.getWidth(), + bufferedImage.getHeight(), + 0, + 0, + bufferedImage.getWidth(), + bufferedImage.getHeight(), + false); } else { - throw new IllegalArgumentException("BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data"); + throw new IllegalArgumentException( + "BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data"); } BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java index c52ff61a..036d6a66 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java @@ -28,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; @@ -39,17 +40,18 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Misc", description = "Miscellaneous APIs") public class BlankPageController { - @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") - @Operation( - summary = "Remove blank pages from a PDF file", - description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - int threshold = request.getThreshold(); - float whitePercent = request.getWhitePercent(); - - PDDocument document = null; + @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") + @Operation( + summary = "Remove blank pages from a PDF file", + description = + "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO") + public ResponseEntity removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + int threshold = request.getThreshold(); + float whitePercent = request.getWhitePercent(); + + PDDocument document = null; try { document = PDDocument.load(inputFile.getInputStream()); PDPageTree pages = document.getDocumentCatalog().getPages(); @@ -72,21 +74,34 @@ public class BlankPageController { boolean hasImages = PdfUtils.hasImagesOnPage(page); if (hasImages) { System.out.println("page " + pageIndex + " has image"); - + Path tempFile = Files.createTempFile("image_", ".png"); - + // Render image and save as temp file BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300); ImageIO.write(image, "png", tempFile.toFile()); - - List command = new ArrayList<>(Arrays.asList("python3", System.getProperty("user.dir") + "/scripts/detect-blank-pages.py", tempFile.toString() ,"--threshold", String.valueOf(threshold), "--white_percent", String.valueOf(whitePercent))); - + + List command = + new ArrayList<>( + Arrays.asList( + "python3", + System.getProperty("user.dir") + + "/scripts/detect-blank-pages.py", + tempFile.toString(), + "--threshold", + String.valueOf(threshold), + "--white_percent", + String.valueOf(whitePercent))); + // Run CLI command - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); - + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) + .runCommandWithOutputHandling(command); + // does contain data if (returnCode.getRc() == 0) { - System.out.println("page " + pageIndex + " has image which is not blank"); + System.out.println( + "page " + pageIndex + " has image which is not blank"); pagesToKeepIndex.add(pageIndex); } else { System.out.println("Skipping, Image was blank for page #" + pageIndex); @@ -94,12 +109,12 @@ public class BlankPageController { } } pageIndex++; - } System.out.print("pagesToKeep=" + pagesToKeepIndex.size()); // Remove pages not present in pagesToKeepIndex - List pageIndices = IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList()); + List pageIndices = + IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList()); Collections.reverse(pageIndices); // Reverse to prevent index shifting during removal for (Integer i : pageIndices) { if (!pagesToKeepIndex.contains(i)) { @@ -107,16 +122,15 @@ public class BlankPageController { } } - return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_blanksRemoved.pdf"); + return WebResponseUtils.pdfDocToWebResponse( + document, + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_blanksRemoved.pdf"); } catch (IOException e) { e.printStackTrace(); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } finally { - if (document != null) - document.close(); + if (document != null) document.close(); } } - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index dd864bc1..fd9a0460 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -30,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.OptimizePdfRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; @@ -44,20 +45,23 @@ public class CompressController { private static final Logger logger = LoggerFactory.getLogger(CompressController.class); @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") - @Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO") - public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest request) throws Exception { + @Operation( + summary = "Optimize PDF file", + description = + "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO") + public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest request) + throws Exception { MultipartFile inputFile = request.getFileInput(); Integer optimizeLevel = request.getOptimizeLevel(); String expectedOutputSizeString = request.getExpectedOutputSize(); - - if(expectedOutputSizeString == null && optimizeLevel == null) { + if (expectedOutputSizeString == null && optimizeLevel == null) { throw new Exception("Both expected output size and optimize level are not specified"); } Long expectedOutputSize = 0L; boolean autoMode = false; - if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) { + if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1) { expectedOutputSize = GeneralUtils.convertSizeToBytes(expectedOutputSizeString); autoMode = true; } @@ -71,8 +75,9 @@ public class CompressController { // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Determine initial optimization level based on expected size reduction, only if in autoMode - if(autoMode) { + // Determine initial optimization level based on expected size reduction, only if in + // autoMode + if (autoMode) { double sizeReductionRatio = expectedOutputSize / (double) inputFileSize; if (sizeReductionRatio > 0.7) { optimizeLevel = 1; @@ -94,20 +99,20 @@ public class CompressController { command.add("-dCompatibilityLevel=1.4"); switch (optimizeLevel) { - case 1: - command.add("-dPDFSETTINGS=/prepress"); - break; - case 2: - command.add("-dPDFSETTINGS=/printer"); - break; - case 3: - command.add("-dPDFSETTINGS=/ebook"); - break; - case 4: - command.add("-dPDFSETTINGS=/screen"); - break; - default: - command.add("-dPDFSETTINGS=/default"); + case 1: + command.add("-dPDFSETTINGS=/prepress"); + break; + case 2: + command.add("-dPDFSETTINGS=/printer"); + break; + case 3: + command.add("-dPDFSETTINGS=/ebook"); + break; + case 4: + command.add("-dPDFSETTINGS=/screen"); + break; + default: + command.add("-dPDFSETTINGS=/default"); } command.add("-dNOPAUSE"); @@ -116,7 +121,9 @@ public class CompressController { command.add("-sOutputFile=" + tempOutputFile.toString()); command.add(tempInputFile.toString()); - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(command); // Check if file size is within expected size or not auto mode so instantly finish long outputFileSize = Files.size(tempOutputFile); @@ -125,19 +132,18 @@ public class CompressController { } else { // Increase optimization level for next iteration optimizeLevel++; - if(autoMode && optimizeLevel > 3) { + if (autoMode && optimizeLevel > 3) { System.out.println("Skipping level 4 due to bad results in auto mode"); sizeMet = true; - } else if(optimizeLevel == 5) { - + } else if (optimizeLevel == 5) { + } else { - System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); + System.out.println( + "Increasing ghostscript optimisation level to " + optimizeLevel); } } } - - if (expectedOutputSize != null && autoMode) { long outputFileSize = Files.size(tempOutputFile); if (outputFileSize > expectedOutputSize) { @@ -157,8 +163,8 @@ public class CompressController { BufferedImage bufferedImage = image.getImage(); // Calculate the new dimensions - int newWidth = (int)(bufferedImage.getWidth() * scaleFactor); - int newHeight = (int)(bufferedImage.getHeight() * scaleFactor); + int newWidth = (int) (bufferedImage.getWidth() * scaleFactor); + int newHeight = (int) (bufferedImage.getHeight() * scaleFactor); // If the new dimensions are zero, skip this iteration if (newWidth == 0 || newHeight == 0) { @@ -166,23 +172,39 @@ public class CompressController { } // Otherwise, proceed with the scaling - Image scaledImage = bufferedImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + Image scaledImage = + bufferedImage.getScaledInstance( + newWidth, newHeight, Image.SCALE_SMOOTH); // Convert the scaled image back to a BufferedImage - BufferedImage scaledBufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); - scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null); + BufferedImage scaledBufferedImage = + new BufferedImage( + newWidth, + newHeight, + BufferedImage.TYPE_INT_RGB); + scaledBufferedImage + .getGraphics() + .drawImage(scaledImage, 0, 0, null); // Compress the scaled image - ByteArrayOutputStream compressedImageStream = new ByteArrayOutputStream(); - ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream); + ByteArrayOutputStream compressedImageStream = + new ByteArrayOutputStream(); + ImageIO.write( + scaledBufferedImage, "jpeg", compressedImageStream); byte[] imageBytes = compressedImageStream.toByteArray(); compressedImageStream.close(); // Convert compressed image back to PDImageXObject - ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes); - PDImageXObject compressedImage = PDImageXObject.createFromByteArray(doc, imageBytes, image.getCOSObject().toString()); + ByteArrayInputStream bais = + new ByteArrayInputStream(imageBytes); + PDImageXObject compressedImage = + PDImageXObject.createFromByteArray( + doc, + imageBytes, + image.getCOSObject().toString()); - // Replace the image in the resources with the compressed version + // Replace the image in the resources with the compressed + // version res.put(name, compressedImage); } } @@ -194,16 +216,23 @@ public class CompressController { long currentSize = Files.size(tempOutputFile); // Check if the overall PDF size is still larger than expectedOutputSize if (currentSize > expectedOutputSize) { - // Log the current file size and scaleFactor - - System.out.println("Current file size: " + FileUtils.byteCountToDisplaySize(currentSize)); + // Log the current file size and scaleFactor + + System.out.println( + "Current file size: " + + FileUtils.byteCountToDisplaySize(currentSize)); System.out.println("Current scale factor: " + scaleFactor); // The file is still too large, reduce scaleFactor and try again scaleFactor *= 0.9; // reduce scaleFactor by 10% // Avoid scaleFactor being too small, causing the image to shrink to 0 - if(scaleFactor < 0.2 || previousFileSize == currentSize){ - throw new RuntimeException("Could not reach the desired size without excessively degrading image quality, lowest size recommended is " + FileUtils.byteCountToDisplaySize(currentSize) + ", " + currentSize + " bytes"); + if (scaleFactor < 0.2 || previousFileSize == currentSize) { + throw new RuntimeException( + "Could not reach the desired size without excessively degrading image quality, lowest size recommended is " + + FileUtils.byteCountToDisplaySize(currentSize) + + ", " + + currentSize + + " bytes"); } previousFileSize = currentSize; } else { @@ -211,10 +240,7 @@ public class CompressController { break; } } - } - - } } @@ -222,9 +248,10 @@ public class CompressController { byte[] pdfBytes = Files.readAllBytes(tempOutputFile); // Check if optimized file is larger than the original - if(pdfBytes.length > inputFileSize) { + if (pdfBytes.length > inputFileSize) { // Log the occurrence - logger.warn("Optimized file is larger than the original. Returning the original file instead."); + logger.warn( + "Optimized file is larger than the original. Returning the original file instead."); // Read the original file again pdfBytes = Files.readAllBytes(tempInputFile); @@ -235,8 +262,8 @@ public class CompressController { Files.delete(tempOutputFile); // Return the optimized PDF as a response - String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf"; + String outputFilename = + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf"; return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index d5906970..257f4d52 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -32,10 +32,12 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/misc") @Tag(name = "Misc", description = "Miscellaneous APIs") @@ -44,18 +46,28 @@ public class ExtractImageScansController { private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class); @PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans") - @Operation(summary = "Extract image scans from an input file", - description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO") + @Operation( + summary = "Extract image scans from an input file", + description = + "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO") public ResponseEntity extractImageScans( - @RequestBody( - description = "Form data containing file and extraction parameters", - required = true, - content = @Content( - mediaType = "multipart/form-data", - schema = @Schema(implementation = ExtractImageScansRequest.class) // This should represent your form's structure - ) - ) - ExtractImageScansRequest form) throws IOException, InterruptedException { + @RequestBody( + description = "Form data containing file and extraction parameters", + required = true, + content = + @Content( + mediaType = "multipart/form-data", + schema = + @Schema( + implementation = + ExtractImageScansRequest + .class) // This should + // represent + // your form's + // structure + )) + ExtractImageScansRequest form) + throws IOException, InterruptedException { String fileName = form.getFileInput().getOriginalFilename(); String extension = fileName.substring(fileName.lastIndexOf(".") + 1); @@ -64,7 +76,8 @@ public class ExtractImageScansController { // Check if input file is a PDF if (extension.equalsIgnoreCase("pdf")) { // Load PDF document - try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) { + try (PDDocument document = + PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) { PDFRenderer pdfRenderer = new PDFRenderer(document); int pageCount = document.getNumberOfPages(); images = new ArrayList<>(); @@ -84,7 +97,10 @@ public class ExtractImageScansController { } } else { Path tempInputFile = Files.createTempFile("input_", "." + extension); - Files.copy(form.getFileInput().getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + Files.copy( + form.getFileInput().getInputStream(), + tempInputFile, + StandardCopyOption.REPLACE_EXISTING); // Add input file path to images list images.add(tempInputFile.toString()); } @@ -95,21 +111,28 @@ public class ExtractImageScansController { for (int i = 0; i < images.size(); i++) { Path tempDir = Files.createTempDirectory("openCV_output"); - List command = new ArrayList<>(Arrays.asList( - "python3", - "./scripts/split_photos.py", - images.get(i), - tempDir.toString(), - "--angle_threshold", String.valueOf(form.getAngleThreshold()), - "--tolerance", String.valueOf(form.getTolerance()), - "--min_area", String.valueOf(form.getMinArea()), - "--min_contour_area", String.valueOf(form.getMinContourArea()), - "--border_size", String.valueOf(form.getBorderSize()) - )); - + List command = + new ArrayList<>( + Arrays.asList( + "python3", + "./scripts/split_photos.py", + images.get(i), + tempDir.toString(), + "--angle_threshold", + String.valueOf(form.getAngleThreshold()), + "--tolerance", + String.valueOf(form.getTolerance()), + "--min_area", + String.valueOf(form.getMinArea()), + "--min_contour_area", + String.valueOf(form.getMinContourArea()), + "--border_size", + String.valueOf(form.getBorderSize()))); // Run CLI command - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) + .runCommandWithOutputHandling(command); // Read the output photos in temp directory List tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList()); @@ -126,10 +149,16 @@ public class ExtractImageScansController { String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip"; Path tempZipFile = Files.createTempFile("output_", ".zip"); - try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { + try (ZipOutputStream zipOut = + new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { // Add processed images to the zip for (int i = 0; i < processedImageBytes.size(); i++) { - ZipEntry entry = new ZipEntry(fileName.replaceFirst("[.][^.]+$", "") + "_" + (i + 1) + ".png"); + ZipEntry entry = + new ZipEntry( + fileName.replaceFirst("[.][^.]+$", "") + + "_" + + (i + 1) + + ".png"); zipOut.putNextEntry(entry); zipOut.write(processedImageBytes.get(i)); zipOut.closeEntry(); @@ -141,13 +170,15 @@ public class ExtractImageScansController { // Clean up the temporary zip file Files.delete(tempZipFile); - return WebResponseUtils.bytesToWebResponse(zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse( + zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); } else { // Return the processed image as a response byte[] imageBytes = processedImageBytes.get(0); - return WebResponseUtils.bytesToWebResponse(imageBytes, fileName.replaceFirst("[.][^.]+$", "") + ".png", MediaType.IMAGE_PNG); + return WebResponseUtils.bytesToWebResponse( + imageBytes, + fileName.replaceFirst("[.][^.]+$", "") + ".png", + MediaType.IMAGE_PNG); } - } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index 6e18f1f2..f436d9f6 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.api.misc; + import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; @@ -29,8 +30,10 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFWithImageFormatRequest; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/misc") @Tag(name = "Misc", description = "Miscellaneous APIs") @@ -39,13 +42,17 @@ public class ExtractImagesController { private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class); @PostMapping(consumes = "multipart/form-data", value = "/extract-images") - @Operation(summary = "Extract images from a PDF file", - description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO") - public ResponseEntity extractImages(@ModelAttribute PDFWithImageFormatRequest request) throws IOException { + @Operation( + summary = "Extract images from a PDF file", + description = + "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO") + public ResponseEntity extractImages(@ModelAttribute PDFWithImageFormatRequest request) + throws IOException { MultipartFile file = request.getFileInput(); String format = request.getFormat(); - System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); + System.out.println( + System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); PDDocument document = PDDocument.load(file.getBytes()); // Create ByteArrayOutputStream to write zip file to byte array @@ -69,24 +76,37 @@ public class ExtractImagesController { if (page.getResources().isImageXObject(name)) { PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name); int imageHash = image.hashCode(); - if(processedImages.contains(imageHash)) { + if (processedImages.contains(imageHash)) { continue; // Skip already processed images } processedImages.add(imageHash); - + // Convert image to desired format RenderedImage renderedImage = image.getImage(); BufferedImage bufferedImage = null; if (format.equalsIgnoreCase("png")) { - bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + bufferedImage = + new BufferedImage( + renderedImage.getWidth(), + renderedImage.getHeight(), + BufferedImage.TYPE_INT_ARGB); } else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) { - bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_RGB); + bufferedImage = + new BufferedImage( + renderedImage.getWidth(), + renderedImage.getHeight(), + BufferedImage.TYPE_INT_RGB); } else if (format.equalsIgnoreCase("gif")) { - bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED); + bufferedImage = + new BufferedImage( + renderedImage.getWidth(), + renderedImage.getHeight(), + BufferedImage.TYPE_BYTE_INDEXED); } // Write image to zip file - String imageName = filename + "_" + imageIndex + " (Page " + pageNum + ")." + format; + String imageName = + filename + "_" + imageIndex + " (Page " + pageNum + ")." + format; ZipEntry zipEntry = new ZipEntry(imageName); zos.putNextEntry(zipEntry); @@ -111,7 +131,7 @@ public class ExtractImagesController { // Create ByteArrayResource from byte array byte[] zipContents = baos.toByteArray(); - return WebResponseUtils.boasToWebResponse(baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.boasToWebResponse( + baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java b/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java index 9e9d8ba6..e9885f1e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java @@ -3,21 +3,17 @@ package stirling.software.SPDF.controller.api.misc; import java.awt.Color; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; - import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.awt.image.RescaleOp; import java.io.ByteArrayOutputStream; - import java.io.File; import java.io.IOException; import java.security.SecureRandom; - import java.util.Random; - import javax.imageio.ImageIO; import org.apache.pdfbox.pdmodel.PDDocument; @@ -40,6 +36,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; @@ -50,102 +47,101 @@ public class FakeScanControllerWIP { private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class); - //TODO + // TODO @Hidden @PostMapping(consumes = "multipart/form-data", value = "/fakeScan") @Operation( - summary = "Repair a PDF file", - description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response." - ) + summary = "Repair a PDF file", + description = + "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.") public ResponseEntity repairPdf(@ModelAttribute PDFFile request) throws IOException { MultipartFile inputFile = request.getFileInput(); - PDDocument document = PDDocument.load(inputFile.getBytes()); - PDFRenderer pdfRenderer = new PDFRenderer(document); - for (int page = 0; page < document.getNumberOfPages(); ++page) - { - BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); - ImageIO.write(image, "png", new File("scanned-" + (page+1) + ".png")); - } - document.close(); + PDDocument document = PDDocument.load(inputFile.getBytes()); + PDFRenderer pdfRenderer = new PDFRenderer(document); + for (int page = 0; page < document.getNumberOfPages(); ++page) { + BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); + ImageIO.write(image, "png", new File("scanned-" + (page + 1) + ".png")); + } + document.close(); - // Constants - int scannedness = 90; // Value between 0 and 100 - int dirtiness = 0; // Value between 0 and 100 + // Constants + int scannedness = 90; // Value between 0 and 100 + int dirtiness = 0; // Value between 0 and 100 - // Load the source image - BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png")); + // Load the source image + BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png")); - // Create the destination image - BufferedImage destinationImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType()); + // Create the destination image + BufferedImage destinationImage = + new BufferedImage( + sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType()); - // Apply a brightness and contrast effect based on the "scanned-ness" - float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5 - float offset = scannedness * 1.5f; // Between 0 and 150 - BufferedImageOp op = new RescaleOp(scaleFactor, offset, null); - op.filter(sourceImage, destinationImage); + // Apply a brightness and contrast effect based on the "scanned-ness" + float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5 + float offset = scannedness * 1.5f; // Between 0 and 150 + BufferedImageOp op = new RescaleOp(scaleFactor, offset, null); + op.filter(sourceImage, destinationImage); - // Apply a rotation effect - double rotationRequired = Math.toRadians((new SecureRandom().nextInt(3 - 1) + 1)); // Random angle between 1 and 3 degrees - double locationX = destinationImage.getWidth() / 2; - double locationY = destinationImage.getHeight() / 2; - AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY); - AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR); - destinationImage = rotateOp.filter(destinationImage, null); + // Apply a rotation effect + double rotationRequired = + Math.toRadians( + (new SecureRandom().nextInt(3 - 1) + + 1)); // Random angle between 1 and 3 degrees + double locationX = destinationImage.getWidth() / 2; + double locationY = destinationImage.getHeight() / 2; + AffineTransform tx = + AffineTransform.getRotateInstance(rotationRequired, locationX, locationY); + AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR); + destinationImage = rotateOp.filter(destinationImage, null); - // Apply a blur effect based on the "scanned-ness" - float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2 - float[] matrix = { - blurIntensity, blurIntensity, blurIntensity, - blurIntensity, blurIntensity, blurIntensity, - blurIntensity, blurIntensity, blurIntensity - }; - BufferedImageOp blurOp = new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null); - destinationImage = blurOp.filter(destinationImage, null); + // Apply a blur effect based on the "scanned-ness" + float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2 + float[] matrix = { + blurIntensity, blurIntensity, blurIntensity, + blurIntensity, blurIntensity, blurIntensity, + blurIntensity, blurIntensity, blurIntensity + }; + BufferedImageOp blurOp = + new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null); + destinationImage = blurOp.filter(destinationImage, null); - // Add noise to the image based on the "dirtiness" - Random random = new SecureRandom(); - for (int y = 0; y < destinationImage.getHeight(); y++) { - for (int x = 0; x < destinationImage.getWidth(); x++) { - if (random.nextInt(100) < dirtiness) { - // Change the pixel color to black randomly based on the "dirtiness" - destinationImage.setRGB(x, y, Color.BLACK.getRGB()); - } - } - } + // Add noise to the image based on the "dirtiness" + Random random = new SecureRandom(); + for (int y = 0; y < destinationImage.getHeight(); y++) { + for (int x = 0; x < destinationImage.getWidth(); x++) { + if (random.nextInt(100) < dirtiness) { + // Change the pixel color to black randomly based on the "dirtiness" + destinationImage.setRGB(x, y, Color.BLACK.getRGB()); + } + } + } - // Save the image - ImageIO.write(destinationImage, "PNG", new File("scanned-1.png")); + // Save the image + ImageIO.write(destinationImage, "PNG", new File("scanned-1.png")); + PDDocument documentOut = new PDDocument(); + for (int page = 1; page <= document.getNumberOfPages(); ++page) { + BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png")); - - - - + // Adjust the dimensions of the page + PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1)); + documentOut.addPage(pdPage); - PDDocument documentOut = new PDDocument(); - for (int page = 1; page <= document.getNumberOfPages(); ++page) - { - BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png")); - - // Adjust the dimensions of the page - PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1)); - documentOut.addPage(pdPage); - - PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim); - PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage); - - // Draw the image with a slight offset and enlarged dimensions - contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2); - contentStream.close(); - } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - documentOut.save(baos); - documentOut.close(); + PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim); + PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage); + + // Draw the image with a slight offset and enlarged dimensions + contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2); + contentStream.close(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + documentOut.save(baos); + documentOut.close(); // Return the optimized PDF as a response - String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf"; + String outputFilename = + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf"; return WebResponseUtils.boasToWebResponse(baos, outputFilename); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java index 027c6240..62783dc4 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java @@ -19,6 +19,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.MetadataRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -27,7 +28,6 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Misc", description = "Miscellaneous APIs") public class MetadataController { - private String checkUndefined(String entry) { // Check if the string is "undefined" if ("undefined".equals(entry)) { @@ -36,14 +36,16 @@ public class MetadataController { } // Return the original string if it's not "undefined" return entry; - } @PostMapping(consumes = "multipart/form-data", value = "/update-metadata") - @Operation(summary = "Update metadata of a PDF file", - description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO") - public ResponseEntity metadata(@ModelAttribute MetadataRequest request) throws IOException { - + @Operation( + summary = "Update metadata of a PDF file", + description = + "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO") + public ResponseEntity metadata(@ModelAttribute MetadataRequest request) + throws IOException { + // Extract PDF file from the request object MultipartFile pdfFile = request.getFileInput(); @@ -61,8 +63,8 @@ public class MetadataController { // Extract additional custom parameters Map allRequestParams = request.getAllRequestParams(); - if(allRequestParams == null) { - allRequestParams = new java.util.HashMap(); + if (allRequestParams == null) { + allRequestParams = new java.util.HashMap(); } // Load the PDF file into a PDDocument PDDocument document = PDDocument.load(pdfFile.getBytes()); @@ -89,7 +91,9 @@ public class MetadataController { } // Remove metadata from the PDF history document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName("Metadata")); - document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName("PieceInfo")); + document.getDocumentCatalog() + .getCOSObject() + .removeItem(COSName.getPDFName("PieceInfo")); author = null; creationDate = null; creator = null; @@ -104,9 +108,17 @@ public class MetadataController { for (Entry entry : allRequestParams.entrySet()) { String key = entry.getKey(); // Check if the key is a standard metadata key - if (!key.equalsIgnoreCase("Author") && !key.equalsIgnoreCase("CreationDate") && !key.equalsIgnoreCase("Creator") && !key.equalsIgnoreCase("Keywords") - && !key.equalsIgnoreCase("modificationDate") && !key.equalsIgnoreCase("Producer") && !key.equalsIgnoreCase("Subject") && !key.equalsIgnoreCase("Title") - && !key.equalsIgnoreCase("Trapped") && !key.contains("customKey") && !key.contains("customValue")) { + if (!key.equalsIgnoreCase("Author") + && !key.equalsIgnoreCase("CreationDate") + && !key.equalsIgnoreCase("Creator") + && !key.equalsIgnoreCase("Keywords") + && !key.equalsIgnoreCase("modificationDate") + && !key.equalsIgnoreCase("Producer") + && !key.equalsIgnoreCase("Subject") + && !key.equalsIgnoreCase("Title") + && !key.equalsIgnoreCase("Trapped") + && !key.contains("customKey") + && !key.contains("customValue")) { info.setCustomMetadataValue(key, entry.getValue()); } else if (key.contains("customKey")) { int number = Integer.parseInt(key.replaceAll("\\D", "")); @@ -119,7 +131,8 @@ public class MetadataController { if (creationDate != null && creationDate.length() > 0) { Calendar creationDateCal = Calendar.getInstance(); try { - creationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate)); + creationDateCal.setTime( + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate)); } catch (ParseException e) { e.printStackTrace(); } @@ -130,7 +143,8 @@ public class MetadataController { if (modificationDate != null && modificationDate.length() > 0) { Calendar modificationDateCal = Calendar.getInstance(); try { - modificationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate)); + modificationDateCal.setTime( + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate)); } catch (ParseException e) { e.printStackTrace(); } @@ -147,7 +161,8 @@ public class MetadataController { info.setTrapped(trapped); document.setDocumentInformation(info); - return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf"); + return WebResponseUtils.pdfDocToWebResponse( + document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf"); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java index 5ea1818e..21cf2b1c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java @@ -26,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -44,14 +45,21 @@ public class OCRController { if (files == null) { return Collections.emptyList(); } - return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", "")) - .filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList()); + return Arrays.stream(files) + .filter(file -> file.getName().endsWith(".traineddata")) + .map(file -> file.getName().replace(".traineddata", "")) + .filter(lang -> !lang.equalsIgnoreCase("osd")) + .collect(Collectors.toList()); } @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf") - @Operation(summary = "Process a PDF file with OCR", - description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional") - public ResponseEntity processPdfWithOCR(@ModelAttribute ProcessPdfWithOcrRequest request) throws IOException, InterruptedException { + @Operation( + summary = "Process a PDF file with OCR", + description = + "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional") + public ResponseEntity processPdfWithOCR( + @ModelAttribute ProcessPdfWithOcrRequest request) + throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); List selectedLanguages = request.getLanguages(); Boolean sidecar = request.isSidecar(); @@ -65,16 +73,17 @@ public class OCRController { if (selectedLanguages == null || selectedLanguages.isEmpty()) { throw new IOException("Please select at least one language."); } - - if(!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) { + + if (!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) { throw new IOException("ocrRenderType wrong"); } - + // Get available Tesseract languages List availableLanguages = getAvailableTesseractLanguages(); // Validate selected languages - selectedLanguages = selectedLanguages.stream().filter(availableLanguages::contains).toList(); + selectedLanguages = + selectedLanguages.stream().filter(availableLanguages::contains).toList(); if (selectedLanguages.isEmpty()) { throw new IOException("None of the selected languages are valid."); @@ -92,8 +101,16 @@ public class OCRController { // Run OCR Command String languageOption = String.join("+", selectedLanguages); - - List command = new ArrayList<>(Arrays.asList("ocrmypdf", "--verbose", "2", "--output-type", "pdf", "--pdf-renderer" , ocrRenderType)); + List command = + new ArrayList<>( + Arrays.asList( + "ocrmypdf", + "--verbose", + "2", + "--output-type", + "pdf", + "--pdf-renderer", + ocrRenderType)); if (sidecar != null && sidecar) { sidecarTextPath = Files.createTempFile("sidecar", ".txt"); @@ -120,42 +137,61 @@ public class OCRController { } } - command.addAll(Arrays.asList("--language", languageOption, tempInputFile.toString(), tempOutputFile.toString())); + command.addAll( + Arrays.asList( + "--language", + languageOption, + tempInputFile.toString(), + tempOutputFile.toString())); // Run CLI command - ProcessExecutorResult result = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); - if(result.getRc() != 0 && result.getMessages().contains("multiprocessing/synchronize.py") && result.getMessages().contains("OSError: [Errno 38] Function not implemented")) { - command.add("--jobs"); - command.add("1"); - result = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + ProcessExecutorResult result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) + .runCommandWithOutputHandling(command); + if (result.getRc() != 0 + && result.getMessages().contains("multiprocessing/synchronize.py") + && result.getMessages().contains("OSError: [Errno 38] Function not implemented")) { + command.add("--jobs"); + command.add("1"); + result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) + .runCommandWithOutputHandling(command); } - - - // Remove images from the OCR processed PDF if the flag is set to true if (removeImagesAfter != null && removeImagesAfter) { Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); - List gsCommand = Arrays.asList("gs", "-sDEVICE=pdfwrite", "-dFILTERIMAGE", "-o", tempPdfWithoutImages.toString(), tempOutputFile.toString()); + List gsCommand = + Arrays.asList( + "gs", + "-sDEVICE=pdfwrite", + "-dFILTERIMAGE", + "-o", + tempPdfWithoutImages.toString(), + tempOutputFile.toString()); - ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand); + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(gsCommand); tempOutputFile = tempPdfWithoutImages; } // Read the OCR processed PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); // Clean up the temporary files Files.delete(tempInputFile); - + // Return the OCR processed PDF as a response - String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf"; + String outputFilename = + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf"; if (sidecar != null && sidecar) { // Create a zip file containing both the PDF and the text file - String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip"; + String outputZipFilename = + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip"; Path tempZipFile = Files.createTempFile("output_", ".zip"); - try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { + try (ZipOutputStream zipOut = + new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { // Add PDF file to the zip ZipEntry pdfEntry = new ZipEntry(outputFilename); zipOut.putNextEntry(pdfEntry); @@ -177,13 +213,12 @@ public class OCRController { Files.delete(sidecarTextPath); // Return the zip file containing both the PDF and the text file - return WebResponseUtils.bytesToWebResponse(zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse( + zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); } else { // Return the OCR processed PDF as a response Files.delete(tempOutputFile); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } - } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java index e28f7535..9fe6249c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java @@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.OverlayImageRequest; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -27,9 +28,9 @@ public class OverlayImageController { @PostMapping(consumes = "multipart/form-data", value = "/add-image") @Operation( - summary = "Overlay image onto a PDF file", - description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO" - ) + summary = "Overlay image onto a PDF file", + description = + "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO") public ResponseEntity overlayImage(@ModelAttribute OverlayImageRequest request) { MultipartFile pdfFile = request.getFileInput(); MultipartFile imageFile = request.getImageFile(); @@ -41,7 +42,9 @@ public class OverlayImageController { byte[] imageBytes = imageFile.getBytes(); byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage); - return WebResponseUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"); + return WebResponseUtils.bytesToWebResponse( + result, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"); } catch (IOException e) { logger.error("Failed to add image to PDF", e); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java index 61a1ec97..6c302524 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java @@ -21,6 +21,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -33,16 +34,20 @@ public class PageNumbersController { private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") - @Operation(summary = "Add page numbers to a PDF document", description = "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") - public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersRequest request) throws IOException { + @Operation( + summary = "Add page numbers to a PDF document", + description = + "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") + public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersRequest request) + throws IOException { MultipartFile file = request.getFileInput(); String customMargin = request.getCustomMargin(); int position = request.getPosition(); int startingNumber = request.getStartingNumber(); String pagesToNumber = request.getPagesToNumber(); String customText = request.getCustomText(); - int pageNumber = startingNumber; - byte[] fileBytes = file.getBytes(); + int pageNumber = startingNumber; + byte[] fileBytes = file.getBytes(); PDDocument document = PDDocument.load(fileBytes); float marginFactor; @@ -58,9 +63,8 @@ public class PageNumbersController { break; case "x-large": marginFactor = 0.075f; - break; - - + break; + default: marginFactor = 0.035f; break; @@ -68,19 +72,29 @@ public class PageNumbersController { float fontSize = 12.0f; PDType1Font font = PDType1Font.HELVETICA; - if(pagesToNumber == null || pagesToNumber.length() == 0) { - pagesToNumber = "all"; + if (pagesToNumber == null || pagesToNumber.length() == 0) { + pagesToNumber = "all"; } - if(customText == null || customText.length() == 0) { - customText = "{n}"; + if (customText == null || customText.length() == 0) { + customText = "{n}"; } - List pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); + List pagesToNumberList = + GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); for (int i : pagesToNumberList) { PDPage page = document.getPage(i); PDRectangle pageSize = page.getMediaBox(); - String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(document.getNumberOfPages())).replace("{filename}", file.getOriginalFilename().replaceFirst("[.][^.]+$", "")) : String.valueOf(pageNumber); + String text = + customText != null + ? customText + .replace("{n}", String.valueOf(pageNumber)) + .replace("{total}", String.valueOf(document.getNumberOfPages())) + .replace( + "{filename}", + file.getOriginalFilename() + .replaceFirst("[.][^.]+$", "")) + : String.valueOf(pageNumber); float x, y; @@ -88,10 +102,10 @@ public class PageNumbersController { int yGroup = 2 - (position - 1) / 3; switch (xGroup) { - case 0: // left + case 0: // left x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); break; - case 1: // center + case 1: // center x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); break; default: // right @@ -100,10 +114,10 @@ public class PageNumbersController { } switch (yGroup) { - case 0: // bottom + case 0: // bottom y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); break; - case 1: // middle + case 1: // middle y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); break; default: // top @@ -111,7 +125,9 @@ public class PageNumbersController { break; } - PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); + PDPageContentStream contentStream = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true); contentStream.beginText(); contentStream.setFont(font, fontSize); contentStream.newLineAtOffset(x, y); @@ -126,10 +142,9 @@ public class PageNumbersController { document.save(baos); document.close(); - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", MediaType.APPLICATION_PDF); - + return WebResponseUtils.bytesToWebResponse( + baos.toByteArray(), + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", + MediaType.APPLICATION_PDF); } - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index f9ae541d..112985a3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -17,6 +17,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -31,11 +32,12 @@ public class RepairController { @PostMapping(consumes = "multipart/form-data", value = "/repair") @Operation( - summary = "Repair a PDF file", - description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity repairPdf(@ModelAttribute PDFFile request) throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); + summary = "Repair a PDF file", + description = + "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO") + public ResponseEntity repairPdf(@ModelAttribute PDFFile request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); inputFile.transferTo(tempInputFile.toFile()); @@ -50,8 +52,9 @@ public class RepairController { command.add("-sDEVICE=pdfwrite"); command.add(tempInputFile.toString()); - - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(command); // Read the optimized PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); @@ -61,8 +64,8 @@ public class RepairController { Files.delete(tempOutputFile); // Return the optimized PDF as a response - String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_repaired.pdf"; + String outputFilename = + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_repaired.pdf"; return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java index cef32ac7..ed7852fa 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java @@ -17,47 +17,60 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/misc") @Tag(name = "Misc", description = "Miscellaneous APIs") public class ShowJavascript { private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); + @PostMapping(consumes = "multipart/form-data", value = "/show-javascript") - @Operation(summary = "Grabs all JS from a PDF and returns a single JS file with all code", description = "desc. Input:PDF Output:JS Type:SISO") + @Operation( + summary = "Grabs all JS from a PDF and returns a single JS file with all code", + description = "desc. Input:PDF Output:JS Type:SISO") public ResponseEntity extractHeader(@ModelAttribute PDFFile request) throws Exception { - MultipartFile inputFile = request.getFileInput(); + MultipartFile inputFile = request.getFileInput(); String script = ""; try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { - - if(document.getDocumentCatalog() != null && document.getDocumentCatalog().getNames() != null) { - PDNameTreeNode jsTree = document.getDocumentCatalog().getNames().getJavaScript(); - - if (jsTree != null) { - Map jsEntries = jsTree.getNames(); - - for (Map.Entry entry : jsEntries.entrySet()) { - String name = entry.getKey(); - PDActionJavaScript jsAction = entry.getValue(); - String jsCodeStr = jsAction.getAction(); - - script += "// File: " + inputFile.getOriginalFilename() + ", Script: " + name + "\n" + jsCodeStr + "\n"; - } - } - } - if (script.isEmpty()) { - script = "PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript"; + if (document.getDocumentCatalog() != null + && document.getDocumentCatalog().getNames() != null) { + PDNameTreeNode jsTree = + document.getDocumentCatalog().getNames().getJavaScript(); + + if (jsTree != null) { + Map jsEntries = jsTree.getNames(); + + for (Map.Entry entry : jsEntries.entrySet()) { + String name = entry.getKey(); + PDActionJavaScript jsAction = entry.getValue(); + String jsCodeStr = jsAction.getAction(); + + script += + "// File: " + + inputFile.getOriginalFilename() + + ", Script: " + + name + + "\n" + + jsCodeStr + + "\n"; + } + } } - return WebResponseUtils.bytesToWebResponse(script.getBytes(StandardCharsets.UTF_8), inputFile.getOriginalFilename() + ".js"); + if (script.isEmpty()) { + script = + "PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript"; + } + + return WebResponseUtils.bytesToWebResponse( + script.getBytes(StandardCharsets.UTF_8), + inputFile.getOriginalFilename() + ".js"); } } - - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java index c3bf9c8d..6ed5f51d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java @@ -1,9 +1,11 @@ package stirling.software.SPDF.controller.api.pipeline; + import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; @@ -17,44 +19,39 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletContext; - import stirling.software.SPDF.SPdfApplication; import stirling.software.SPDF.model.ApiEndpoint; import stirling.software.SPDF.model.Role; -import org.slf4j.Logger; + @Service public class ApiDocService { private final Map apiDocumentation = new HashMap<>(); private static final Logger logger = LoggerFactory.getLogger(ApiDocService.class); - - @Autowired - private ServletContext servletContext; + + @Autowired private ServletContext servletContext; private String getApiDocsUrl() { String contextPath = servletContext.getContextPath(); String port = SPdfApplication.getPort(); - return "http://localhost:"+ port + contextPath + "/v1/api-docs"; + return "http://localhost:" + port + contextPath + "/v1/api-docs"; } - - @Autowired(required=false) - private UserServiceInterface userService; + @Autowired(required = false) + private UserServiceInterface userService; - private String getApiKeyForUser() { - if(userService == null) - return ""; - return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId()); - } - - JsonNode apiDocsJsonRootNode; - - - //@EventListener(ApplicationReadyEvent.class) - private synchronized void loadApiDocumentation() { - String apiDocsJson = ""; + private String getApiKeyForUser() { + if (userService == null) return ""; + return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId()); + } + + JsonNode apiDocsJsonRootNode; + + // @EventListener(ApplicationReadyEvent.class) + private synchronized void loadApiDocumentation() { + String apiDocsJson = ""; try { HttpHeaders headers = new HttpHeaders(); String apiKey = getApiKeyForUser(); @@ -64,49 +61,52 @@ public class ApiDocService { HttpEntity entity = new HttpEntity<>(headers); RestTemplate restTemplate = new RestTemplate(); - ResponseEntity response = restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class); + ResponseEntity response = + restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class); apiDocsJson = response.getBody(); ObjectMapper mapper = new ObjectMapper(); apiDocsJsonRootNode = mapper.readTree(apiDocsJson); JsonNode paths = apiDocsJsonRootNode.path("paths"); - paths.fields().forEachRemaining(entry -> { - String path = entry.getKey(); - JsonNode pathNode = entry.getValue(); - if (pathNode.has("post")) { - JsonNode postNode = pathNode.get("post"); - ApiEndpoint endpoint = new ApiEndpoint(path, postNode); - apiDocumentation.put(path, endpoint); - } - }); + paths.fields() + .forEachRemaining( + entry -> { + String path = entry.getKey(); + JsonNode pathNode = entry.getValue(); + if (pathNode.has("post")) { + JsonNode postNode = pathNode.get("post"); + ApiEndpoint endpoint = new ApiEndpoint(path, postNode); + apiDocumentation.put(path, endpoint); + } + }); } catch (Exception e) { // Handle exceptions - logger.error("Error grabbing swagger doc, body result {}", apiDocsJson); + logger.error("Error grabbing swagger doc, body result {}", apiDocsJson); } } public boolean isValidOperation(String operationName, Map parameters) { - if(apiDocumentation.size() == 0) { - loadApiDocumentation(); - } + if (apiDocumentation.size() == 0) { + loadApiDocumentation(); + } if (!apiDocumentation.containsKey(operationName)) { return false; } ApiEndpoint endpoint = apiDocumentation.get(operationName); return endpoint.areParametersValid(parameters); } - + public boolean isMultiInput(String operationName) { - if(apiDocsJsonRootNode == null || apiDocumentation.size() == 0) { - loadApiDocumentation(); - } - if (!apiDocumentation.containsKey(operationName)) { + if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) { + loadApiDocumentation(); + } + if (!apiDocumentation.containsKey(operationName)) { return false; } ApiEndpoint endpoint = apiDocumentation.get(operationName); - String description = endpoint.getDescription(); + String description = endpoint.getDescription(); Pattern pattern = Pattern.compile("Type:(\\w+)"); Matcher matcher = pattern.matcher(description); @@ -115,9 +115,8 @@ public class ApiDocService { return type.startsWith("MI"); } - return false; + return false; } } // Model class for API Endpoint - diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index a6f78a6f..db5e9661 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.api.HandleDataRequest; @@ -34,84 +35,80 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Pipeline", description = "Pipeline APIs") public class PipelineController { - private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); + private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); - final String watchedFoldersDir = "./pipeline/watchedFolders/"; - final String finishedFoldersDir = "./pipeline/finishedFolders/"; - @Autowired - PipelineProcessor processor; - + final String watchedFoldersDir = "./pipeline/watchedFolders/"; + final String finishedFoldersDir = "./pipeline/finishedFolders/"; + @Autowired PipelineProcessor processor; - @Autowired - ApplicationProperties applicationProperties; - - @Autowired - private ObjectMapper objectMapper; - + @Autowired ApplicationProperties applicationProperties; - @PostMapping("/handleData") - public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) throws JsonMappingException, JsonProcessingException { - if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } + @Autowired private ObjectMapper objectMapper; - MultipartFile[] files = request.getFileInput(); - String jsonString = request.getJson(); - if (files == null) { - return null; - } - PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); - logger.info("Received POST request to /handleData with {} files", files.length); - try { - List inputFiles = processor.generateInputFiles(files); - if(inputFiles == null || inputFiles.size() == 0) { - return null; + @PostMapping("/handleData") + public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) + throws JsonMappingException, JsonProcessingException { + if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + MultipartFile[] files = request.getFileInput(); + String jsonString = request.getJson(); + if (files == null) { + return null; + } + PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); + logger.info("Received POST request to /handleData with {} files", files.length); + try { + List inputFiles = processor.generateInputFiles(files); + if (inputFiles == null || inputFiles.size() == 0) { + return null; } - List outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); - if (outputFiles != null && outputFiles.size() == 1) { - // If there is only one file, return it directly - Resource singleFile = outputFiles.get(0); - InputStream is = singleFile.getInputStream(); - byte[] bytes = new byte[(int) singleFile.contentLength()]; - is.read(bytes); - is.close(); + List outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); + if (outputFiles != null && outputFiles.size() == 1) { + // If there is only one file, return it directly + Resource singleFile = outputFiles.get(0); + InputStream is = singleFile.getInputStream(); + byte[] bytes = new byte[(int) singleFile.contentLength()]; + is.read(bytes); + is.close(); - logger.info("Returning single file response..."); - return WebResponseUtils.bytesToWebResponse(bytes, singleFile.getFilename(), - MediaType.APPLICATION_OCTET_STREAM); - } else if (outputFiles == null) { - return null; - } + logger.info("Returning single file response..."); + return WebResponseUtils.bytesToWebResponse( + bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM); + } else if (outputFiles == null) { + return null; + } - // Create a ByteArrayOutputStream to hold the zip - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ZipOutputStream zipOut = new ZipOutputStream(baos); + // Create a ByteArrayOutputStream to hold the zip + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream zipOut = new ZipOutputStream(baos); - // Loop through each file and add it to the zip - for (Resource file : outputFiles) { - ZipEntry zipEntry = new ZipEntry(file.getFilename()); - zipOut.putNextEntry(zipEntry); + // Loop through each file and add it to the zip + for (Resource file : outputFiles) { + ZipEntry zipEntry = new ZipEntry(file.getFilename()); + zipOut.putNextEntry(zipEntry); - // Read the file into a byte array - InputStream is = file.getInputStream(); - byte[] bytes = new byte[(int) file.contentLength()]; - is.read(bytes); + // Read the file into a byte array + InputStream is = file.getInputStream(); + byte[] bytes = new byte[(int) file.contentLength()]; + is.read(bytes); - // Write the bytes of the file to the zip - zipOut.write(bytes, 0, bytes.length); - zipOut.closeEntry(); + // Write the bytes of the file to the zip + zipOut.write(bytes, 0, bytes.length); + zipOut.closeEntry(); - is.close(); - } + is.close(); + } - zipOut.close(); - - logger.info("Returning zipped file response..."); - return WebResponseUtils.boasToWebResponse(baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); - } catch (Exception e) { - logger.error("Error handling data: ", e); - return null; - } - } + zipOut.close(); + logger.info("Returning zipped file response..."); + return WebResponseUtils.boasToWebResponse( + baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); + } catch (Exception e) { + logger.error("Error handling data: ", e); + return null; + } + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java index dc45d4cb..80fdd71c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java @@ -33,50 +33,48 @@ import stirling.software.SPDF.model.PipelineOperation; @Service public class PipelineDirectoryProcessor { - private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class); - @Autowired - private ObjectMapper objectMapper; - @Autowired - private ApiDocService apiDocService; - @Autowired - private ApplicationProperties applicationProperties; - + private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class); + @Autowired private ObjectMapper objectMapper; + @Autowired private ApiDocService apiDocService; + @Autowired private ApplicationProperties applicationProperties; + final String watchedFoldersDir = "./pipeline/watchedFolders/"; - final String finishedFoldersDir = "./pipeline/finishedFolders/"; - - @Autowired - PipelineProcessor processor; + final String finishedFoldersDir = "./pipeline/finishedFolders/"; + + @Autowired PipelineProcessor processor; @Scheduled(fixedRate = 60000) - public void scanFolders() { - if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { - return; - } - Path watchedFolderPath = Paths.get(watchedFoldersDir); - if (!Files.exists(watchedFolderPath)) { - try { - Files.createDirectories(watchedFolderPath); - logger.info("Created directory: {}", watchedFolderPath); - } catch (IOException e) { - logger.error("Error creating directory: {}", watchedFolderPath, e); - return; - } - } - try (Stream paths = Files.walk(watchedFolderPath)) { - paths.filter(Files::isDirectory).forEach(t -> { - try { - if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) { - handleDirectory(t); - } - } catch (Exception e) { - logger.error("Error handling directory: {}", t, e); - } - }); - } catch (Exception e) { - logger.error("Error walking through directory: {}", watchedFolderPath, e); - } - } - + public void scanFolders() { + if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { + return; + } + Path watchedFolderPath = Paths.get(watchedFoldersDir); + if (!Files.exists(watchedFolderPath)) { + try { + Files.createDirectories(watchedFolderPath); + logger.info("Created directory: {}", watchedFolderPath); + } catch (IOException e) { + logger.error("Error creating directory: {}", watchedFolderPath, e); + return; + } + } + try (Stream paths = Files.walk(watchedFolderPath)) { + paths.filter(Files::isDirectory) + .forEach( + t -> { + try { + if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) { + handleDirectory(t); + } + } catch (Exception e) { + logger.error("Error handling directory: {}", t, e); + } + }); + } catch (Exception e) { + logger.error("Error walking through directory: {}", watchedFolderPath, e); + } + } + public void handleDirectory(Path dir) throws IOException { logger.info("Handling directory: {}", dir); Path processingDir = createProcessingDirectory(dir); @@ -113,13 +111,14 @@ public class PipelineDirectoryProcessor { return objectMapper.readValue(jsonString, PipelineConfig.class); } - private void processPipelineOperations(Path dir, Path processingDir, Path jsonFile, PipelineConfig config) throws IOException { + private void processPipelineOperations( + Path dir, Path processingDir, Path jsonFile, PipelineConfig config) throws IOException { for (PipelineOperation operation : config.getOperations()) { validateOperation(operation); File[] files = collectFilesForProcessing(dir, jsonFile, operation); - if(files == null || files.length == 0) { - logger.debug("No files detected for {} ", dir); - return; + if (files == null || files.length == 0) { + logger.debug("No files detected for {} ", dir); + return; } List filesToProcess = prepareFilesForProcessing(files, processingDir); runPipelineAgainstFiles(filesToProcess, config, dir, processingDir); @@ -132,20 +131,22 @@ public class PipelineDirectoryProcessor { } } - private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation) throws IOException { + private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation) + throws IOException { try (Stream paths = Files.list(dir)) { if ("automated".equals(operation.getParameters().get("fileInput"))) { return paths.filter(path -> !Files.isDirectory(path) && !path.equals(jsonFile)) - .map(Path::toFile) - .toArray(File[]::new); + .map(Path::toFile) + .toArray(File[]::new); } else { String fileInput = (String) operation.getParameters().get("fileInput"); - return new File[]{new File(fileInput)}; + return new File[] {new File(fileInput)}; } } } - private List prepareFilesForProcessing(File[] files, Path processingDir) throws IOException { + private List prepareFilesForProcessing(File[] files, Path processingDir) + throws IOException { List filesToProcess = new ArrayList<>(); for (File file : files) { Path targetPath = resolveUniqueFilePath(processingDir, file.getName()); @@ -173,27 +174,33 @@ public class PipelineDirectoryProcessor { if (dotIndex == -1) { return originalFileName + suffix; } else { - return originalFileName.substring(0, dotIndex) + suffix + originalFileName.substring(dotIndex); + return originalFileName.substring(0, dotIndex) + + suffix + + originalFileName.substring(dotIndex); } } - private void runPipelineAgainstFiles(List filesToProcess, PipelineConfig config, Path dir, Path processingDir) throws IOException { + private void runPipelineAgainstFiles( + List filesToProcess, PipelineConfig config, Path dir, Path processingDir) + throws IOException { try { - List inputFiles = processor.generateInputFiles(filesToProcess.toArray(new File[0])); - if(inputFiles == null || inputFiles.size() == 0) { - return; + List inputFiles = + processor.generateInputFiles(filesToProcess.toArray(new File[0])); + if (inputFiles == null || inputFiles.size() == 0) { + return; } - List outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); + List outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); if (outputFiles == null) return; moveAndRenameFiles(outputFiles, config, dir); deleteOriginalFiles(filesToProcess, processingDir); } catch (Exception e) { - logger.error("error during processing", e); + logger.error("error during processing", e); moveFilesBack(filesToProcess, processingDir); } } - private void moveAndRenameFiles(List resources, PipelineConfig config, Path dir) throws IOException { + private void moveAndRenameFiles(List resources, PipelineConfig config, Path dir) + throws IOException { for (Resource resource : resources) { String outputFileName = createOutputFileName(resource, config); Path outputPath = determineOutputPath(config, dir); @@ -217,26 +224,36 @@ public class PipelineDirectoryProcessor { String baseName = resourceName.substring(0, resourceName.lastIndexOf('.')); String extension = resourceName.substring(resourceName.lastIndexOf('.') + 1); - String outputFileName = config.getOutputPattern() - .replace("{filename}", baseName) - .replace("{pipelineName}", config.getName()) - .replace("{date}", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))) - .replace("{time}", LocalTime.now().format(DateTimeFormatter.ofPattern("HHmmss"))) - + "." + extension; + String outputFileName = + config.getOutputPattern() + .replace("{filename}", baseName) + .replace("{pipelineName}", config.getName()) + .replace( + "{date}", + LocalDate.now() + .format(DateTimeFormatter.ofPattern("yyyyMMdd"))) + .replace( + "{time}", + LocalTime.now() + .format(DateTimeFormatter.ofPattern("HHmmss"))) + + "." + + extension; return outputFileName; } private Path determineOutputPath(PipelineConfig config, Path dir) { - String outputDir = config.getOutputDir() - .replace("{outputFolder}", finishedFoldersDir) - .replace("{folderName}", dir.toString()) - .replaceAll("\\\\?watchedFolders", ""); + String outputDir = + config.getOutputDir() + .replace("{outputFolder}", finishedFoldersDir) + .replace("{folderName}", dir.toString()) + .replaceAll("\\\\?watchedFolders", ""); return Paths.get(outputDir).isAbsolute() ? Paths.get(outputDir) : Paths.get(".", outputDir); } - private void deleteOriginalFiles(List filesToProcess, Path processingDir) throws IOException { + private void deleteOriginalFiles(List filesToProcess, Path processingDir) + throws IOException { for (File file : filesToProcess) { Files.deleteIfExists(processingDir.resolve(file.getName())); logger.info("Deleted original file: {}", file.getName()); @@ -247,12 +264,13 @@ public class PipelineDirectoryProcessor { for (File file : filesToProcess) { try { Files.move(processingDir.resolve(file.getName()), file.toPath()); - logger.info("Moved file back to original location: {} , {}",file.toPath(), file.getName()); + logger.info( + "Moved file back to original location: {} , {}", + file.toPath(), + file.getName()); } catch (IOException e) { logger.error("Error moving file back to original location: {}", file.getName(), e); } } } - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java index 8b4b2ef4..534f3d3b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineProcessor.java @@ -34,7 +34,6 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.ServletContext; - import stirling.software.SPDF.SPdfApplication; import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineOperation; @@ -43,152 +42,160 @@ import stirling.software.SPDF.model.Role; @Service public class PipelineProcessor { - private static final Logger logger = LoggerFactory.getLogger(PipelineProcessor.class); + private static final Logger logger = LoggerFactory.getLogger(PipelineProcessor.class); + @Autowired private ApiDocService apiDocService; - @Autowired - private ApiDocService apiDocService; - - @Autowired(required=false) + @Autowired(required = false) private UserServiceInterface userService; - - @Autowired - private ServletContext servletContext; - + @Autowired private ServletContext servletContext; + private String getApiKeyForUser() { + if (userService == null) return ""; + return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId()); + } - private String getApiKeyForUser() { - if (userService == null) - return ""; - return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId()); - } + private String getBaseUrl() { + String contextPath = servletContext.getContextPath(); + String port = SPdfApplication.getPort(); + return "http://localhost:" + port + contextPath + "/"; + } - private String getBaseUrl() { - String contextPath = servletContext.getContextPath(); - String port = SPdfApplication.getPort(); + List runPipelineAgainstFiles(List outputFiles, PipelineConfig config) + throws Exception { - return "http://localhost:" + port + contextPath + "/"; - } + ByteArrayOutputStream logStream = new ByteArrayOutputStream(); + PrintStream logPrintStream = new PrintStream(logStream); - - - List runPipelineAgainstFiles(List outputFiles, PipelineConfig config) throws Exception { + boolean hasErrors = false; - ByteArrayOutputStream logStream = new ByteArrayOutputStream(); - PrintStream logPrintStream = new PrintStream(logStream); + for (PipelineOperation pipelineOperation : config.getOperations()) { + String operation = pipelineOperation.getOperation(); + boolean isMultiInputOperation = apiDocService.isMultiInput(operation); - boolean hasErrors = false; + logger.info( + "Running operation: {} isMultiInputOperation {}", + operation, + isMultiInputOperation); + Map parameters = pipelineOperation.getParameters(); + String inputFileExtension = ""; - for (PipelineOperation pipelineOperation : config.getOperations()) { - String operation = pipelineOperation.getOperation(); - boolean isMultiInputOperation = apiDocService.isMultiInput(operation); + // TODO + // if (operationNode.has("inputFileType")) { + // inputFileExtension = operationNode.get("inputFileType").asText(); + // } else { + inputFileExtension = ".pdf"; + // } + final String finalInputFileExtension = inputFileExtension; - logger.info("Running operation: {} isMultiInputOperation {}", operation, isMultiInputOperation); - Map parameters = pipelineOperation.getParameters(); - String inputFileExtension = ""; - - //TODO - //if (operationNode.has("inputFileType")) { - // inputFileExtension = operationNode.get("inputFileType").asText(); - //} else { - inputFileExtension = ".pdf"; - //} - final String finalInputFileExtension = inputFileExtension; - - String url = getBaseUrl() + operation; - - List newOutputFiles = new ArrayList<>(); - if (!isMultiInputOperation) { - for (Resource file : outputFiles) { - boolean hasInputFileType = false; - if (file.getFilename().endsWith(inputFileExtension)) { - hasInputFileType = true; - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("fileInput", file); + String url = getBaseUrl() + operation; - - for(Entry entry : parameters.entrySet()) { - body.add(entry.getKey(), entry.getValue()); - } + List newOutputFiles = new ArrayList<>(); + if (!isMultiInputOperation) { + for (Resource file : outputFiles) { + boolean hasInputFileType = false; + if (file.getFilename().endsWith(inputFileExtension)) { + hasInputFileType = true; + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("fileInput", file); - ResponseEntity response = sendWebRequest(url, body); + for (Entry entry : parameters.entrySet()) { + body.add(entry.getKey(), entry.getValue()); + } - // If the operation is filter and the response body is null or empty, skip this - // file - if (operation.startsWith("filter-") - && (response.getBody() == null || response.getBody().length == 0)) { - logger.info("Skipping file due to failing {}", operation); - continue; - } + ResponseEntity response = sendWebRequest(url, body); - if (!response.getStatusCode().equals(HttpStatus.OK)) { - logPrintStream.println("Error: " + response.getBody()); - hasErrors = true; - continue; - } - processOutputFiles(operation, file.getFilename(), response, newOutputFiles); - - } + // If the operation is filter and the response body is null or empty, skip + // this + // file + if (operation.startsWith("filter-") + && (response.getBody() == null || response.getBody().length == 0)) { + logger.info("Skipping file due to failing {}", operation); + continue; + } - if (!hasInputFileType) { - logPrintStream.println( - "No files with extension " + inputFileExtension + " found for operation " + operation); - hasErrors = true; - } + if (!response.getStatusCode().equals(HttpStatus.OK)) { + logPrintStream.println("Error: " + response.getBody()); + hasErrors = true; + continue; + } + processOutputFiles(operation, file.getFilename(), response, newOutputFiles); + } - outputFiles = newOutputFiles; - } + if (!hasInputFileType) { + logPrintStream.println( + "No files with extension " + + inputFileExtension + + " found for operation " + + operation); + hasErrors = true; + } - } else { - // Filter and collect all files that match the inputFileExtension - List matchingFiles = outputFiles.stream() - .filter(file -> file.getFilename().endsWith(finalInputFileExtension)) - .collect(Collectors.toList()); + outputFiles = newOutputFiles; + } - // Check if there are matching files - if (!matchingFiles.isEmpty()) { - // Create a new MultiValueMap for the request body - MultiValueMap body = new LinkedMultiValueMap<>(); + } else { + // Filter and collect all files that match the inputFileExtension + List matchingFiles = + outputFiles.stream() + .filter( + file -> + file.getFilename() + .endsWith(finalInputFileExtension)) + .collect(Collectors.toList()); - // Add all matching files to the body - for (Resource file : matchingFiles) { - body.add("fileInput", file); - } + // Check if there are matching files + if (!matchingFiles.isEmpty()) { + // Create a new MultiValueMap for the request body + MultiValueMap body = new LinkedMultiValueMap<>(); - for(Entry entry : parameters.entrySet()) { - body.add(entry.getKey(), entry.getValue()); - } - - ResponseEntity response = sendWebRequest(url, body); + // Add all matching files to the body + for (Resource file : matchingFiles) { + body.add("fileInput", file); + } - // Handle the response - if (response.getStatusCode().equals(HttpStatus.OK)) { - processOutputFiles(operation, matchingFiles.get(0).getFilename(), response, newOutputFiles); - } else { - // Log error if the response status is not OK - logPrintStream.println("Error in multi-input operation: " + response.getBody()); - hasErrors = true; - } - } else { - logPrintStream.println("No files with extension " + inputFileExtension + " found for multi-input operation " + operation); - hasErrors = true; - } - } - logPrintStream.close(); + for (Entry entry : parameters.entrySet()) { + body.add(entry.getKey(), entry.getValue()); + } - } - if (hasErrors) { - logger.error("Errors occurred during processing. Log: {}", logStream.toString()); - } - return outputFiles; - } + ResponseEntity response = sendWebRequest(url, body); - private ResponseEntity sendWebRequest(String url, MultiValueMap body ){ - RestTemplate restTemplate = new RestTemplate(); - - // Set up headers, including API key + // Handle the response + if (response.getStatusCode().equals(HttpStatus.OK)) { + processOutputFiles( + operation, + matchingFiles.get(0).getFilename(), + response, + newOutputFiles); + } else { + // Log error if the response status is not OK + logPrintStream.println( + "Error in multi-input operation: " + response.getBody()); + hasErrors = true; + } + } else { + logPrintStream.println( + "No files with extension " + + inputFileExtension + + " found for multi-input operation " + + operation); + hasErrors = true; + } + } + logPrintStream.close(); + } + if (hasErrors) { + logger.error("Errors occurred during processing. Log: {}", logStream.toString()); + } + return outputFiles; + } + + private ResponseEntity sendWebRequest(String url, MultiValueMap body) { + RestTemplate restTemplate = new RestTemplate(); + + // Set up headers, including API key HttpHeaders headers = new HttpHeaders(); String apiKey = getApiKeyForUser(); headers.add("X-API-Key", apiKey); @@ -199,134 +206,141 @@ public class PipelineProcessor { // Make the request to the REST endpoint return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class); - } - - private List processOutputFiles(String operation, String fileName, ResponseEntity response, List newOutputFiles) throws IOException{ - // Define filename - String newFilename; - if ("auto-rename".equals(operation)) { - // If the operation is "auto-rename", generate a new filename. - // This is a simple example of generating a filename using current timestamp. - // Modify as per your needs. - newFilename = "file_" + System.currentTimeMillis(); - } else { - // Otherwise, keep the original filename. - newFilename = fileName; - } + } - // Check if the response body is a zip file - if (isZip(response.getBody())) { - // Unzip the file and add all the files to the new output files - newOutputFiles.addAll(unzip(response.getBody())); - } else { - Resource outputResource = new ByteArrayResource(response.getBody()) { - @Override - public String getFilename() { - return newFilename; - } - }; - newOutputFiles.add(outputResource); - } - - return newOutputFiles; - - } - List generateInputFiles(File[] files) throws Exception { - if (files == null || files.length == 0) { - logger.info("No files"); - return null; - } + private List processOutputFiles( + String operation, + String fileName, + ResponseEntity response, + List newOutputFiles) + throws IOException { + // Define filename + String newFilename; + if ("auto-rename".equals(operation)) { + // If the operation is "auto-rename", generate a new filename. + // This is a simple example of generating a filename using current timestamp. + // Modify as per your needs. + newFilename = "file_" + System.currentTimeMillis(); + } else { + // Otherwise, keep the original filename. + newFilename = fileName; + } - - List outputFiles = new ArrayList<>(); + // Check if the response body is a zip file + if (isZip(response.getBody())) { + // Unzip the file and add all the files to the new output files + newOutputFiles.addAll(unzip(response.getBody())); + } else { + Resource outputResource = + new ByteArrayResource(response.getBody()) { + @Override + public String getFilename() { + return newFilename; + } + }; + newOutputFiles.add(outputResource); + } - for (File file : files) { - Path path = Paths.get(file.getAbsolutePath()); - logger.info("Reading file: " + path); // debug statement + return newOutputFiles; + } - if (Files.exists(path)) { - Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) { - @Override - public String getFilename() { - return file.getName(); - } - }; - outputFiles.add(fileResource); - } else { - logger.info("File not found: " + path); - } - } - logger.info("Files successfully loaded. Starting processing..."); - return outputFiles; - } + List generateInputFiles(File[] files) throws Exception { + if (files == null || files.length == 0) { + logger.info("No files"); + return null; + } - List generateInputFiles(MultipartFile[] files) throws Exception { - if (files == null || files.length == 0) { - logger.info("No files"); - return null; - } + List outputFiles = new ArrayList<>(); - List outputFiles = new ArrayList<>(); + for (File file : files) { + Path path = Paths.get(file.getAbsolutePath()); + logger.info("Reading file: " + path); // debug statement - for (MultipartFile file : files) { - Resource fileResource = new ByteArrayResource(file.getBytes()) { - @Override - public String getFilename() { - return file.getOriginalFilename(); - } - }; - outputFiles.add(fileResource); - } - logger.info("Files successfully loaded. Starting processing..."); - return outputFiles; - } + if (Files.exists(path)) { + Resource fileResource = + new ByteArrayResource(Files.readAllBytes(path)) { + @Override + public String getFilename() { + return file.getName(); + } + }; + outputFiles.add(fileResource); + } else { + logger.info("File not found: " + path); + } + } + logger.info("Files successfully loaded. Starting processing..."); + return outputFiles; + } - private boolean isZip(byte[] data) { - if (data == null || data.length < 4) { - return false; - } + List generateInputFiles(MultipartFile[] files) throws Exception { + if (files == null || files.length == 0) { + logger.info("No files"); + return null; + } - // Check the first four bytes of the data against the standard zip magic number - return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04; - } + List outputFiles = new ArrayList<>(); - private List unzip(byte[] data) throws IOException { - logger.info("Unzipping data of length: {}", data.length); - List unzippedFiles = new ArrayList<>(); + for (MultipartFile file : files) { + Resource fileResource = + new ByteArrayResource(file.getBytes()) { + @Override + public String getFilename() { + return file.getOriginalFilename(); + } + }; + outputFiles.add(fileResource); + } + logger.info("Files successfully loaded. Starting processing..."); + return outputFiles; + } - try (ByteArrayInputStream bais = new ByteArrayInputStream(data); - ZipInputStream zis = new ZipInputStream(bais)) { + private boolean isZip(byte[] data) { + if (data == null || data.length < 4) { + return false; + } - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; + // Check the first four bytes of the data against the standard zip magic number + return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04; + } - while ((count = zis.read(buffer)) != -1) { - baos.write(buffer, 0, count); - } + private List unzip(byte[] data) throws IOException { + logger.info("Unzipping data of length: {}", data.length); + List unzippedFiles = new ArrayList<>(); - final String filename = entry.getName(); - Resource fileResource = new ByteArrayResource(baos.toByteArray()) { - @Override - public String getFilename() { - return filename; - } - }; + try (ByteArrayInputStream bais = new ByteArrayInputStream(data); + ZipInputStream zis = new ZipInputStream(bais)) { - // If the unzipped file is a zip file, unzip it - if (isZip(baos.toByteArray())) { - logger.info("File {} is a zip file. Unzipping...", filename); - unzippedFiles.addAll(unzip(baos.toByteArray())); - } else { - unzippedFiles.add(fileResource); - } - } - } + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; - logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size()); - return unzippedFiles; - } + while ((count = zis.read(buffer)) != -1) { + baos.write(buffer, 0, count); + } + final String filename = entry.getName(); + Resource fileResource = + new ByteArrayResource(baos.toByteArray()) { + @Override + public String getFilename() { + return filename; + } + }; + + // If the unzipped file is a zip file, unzip it + if (isZip(baos.toByteArray())) { + logger.info("File {} is a zip file. Unzipping...", filename); + unzippedFiles.addAll(unzip(baos.toByteArray())); + } else { + unzippedFiles.add(fileResource); + } + } + } + + logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size()); + return unzippedFiles; + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java index f1203be8..1a60441e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.api.pipeline; + public interface UserServiceInterface { String getApiKeyForUser(String username); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index b0e5f2aa..8990c789 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -53,6 +53,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -61,198 +62,228 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Security", description = "Security APIs") public class CertSignController { - private static final Logger logger = LoggerFactory.getLogger(CertSignController.class); + private static final Logger logger = LoggerFactory.getLogger(CertSignController.class); - static { - Security.addProvider(new BouncyCastleProvider()); - } + static { + Security.addProvider(new BouncyCastleProvider()); + } - @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") - @Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO") - public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception { - MultipartFile pdf = request.getFileInput(); - String certType = request.getCertType(); - MultipartFile privateKeyFile = request.getPrivateKeyFile(); - MultipartFile certFile = request.getCertFile(); - MultipartFile p12File = request.getP12File(); - String password = request.getPassword(); - Boolean showSignature = request.isShowSignature(); - String reason = request.getReason(); - String location = request.getLocation(); - String name = request.getName(); - Integer pageNumber = request.getPageNumber(); + @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") + @Operation( + summary = "Sign PDF with a Digital Certificate", + description = + "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO") + public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) + throws Exception { + MultipartFile pdf = request.getFileInput(); + String certType = request.getCertType(); + MultipartFile privateKeyFile = request.getPrivateKeyFile(); + MultipartFile certFile = request.getCertFile(); + MultipartFile p12File = request.getP12File(); + String password = request.getPassword(); + Boolean showSignature = request.isShowSignature(); + String reason = request.getReason(); + String location = request.getLocation(); + String name = request.getName(); + Integer pageNumber = request.getPageNumber(); - PrivateKey privateKey = null; - X509Certificate cert = null; + PrivateKey privateKey = null; + X509Certificate cert = null; - if (certType != null) { - logger.info("Cert type provided: {}", certType); - switch (certType) { - case "PKCS12": - if (p12File != null) { - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); - String alias = ks.aliases().nextElement(); - if (!ks.isKeyEntry(alias)) { - throw new IllegalArgumentException("The provided PKCS12 file does not contain a private key."); - } - privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); - cert = (X509Certificate) ks.getCertificate(alias); - } - break; - case "PEM": - if (privateKeyFile != null && certFile != null) { - // Load private key - KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); - if (isPEM(privateKeyFile.getBytes())) { - privateKey = keyFactory - .generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); - } else { - privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); - } + if (certType != null) { + logger.info("Cert type provided: {}", certType); + switch (certType) { + case "PKCS12": + if (p12File != null) { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load( + new ByteArrayInputStream(p12File.getBytes()), + password.toCharArray()); + String alias = ks.aliases().nextElement(); + if (!ks.isKeyEntry(alias)) { + throw new IllegalArgumentException( + "The provided PKCS12 file does not contain a private key."); + } + privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); + cert = (X509Certificate) ks.getCertificate(alias); + } + break; + case "PEM": + if (privateKeyFile != null && certFile != null) { + // Load private key + KeyFactory keyFactory = + KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); + if (isPEM(privateKeyFile.getBytes())) { + privateKey = + keyFactory.generatePrivate( + new PKCS8EncodedKeySpec( + parsePEM(privateKeyFile.getBytes()))); + } else { + privateKey = + keyFactory.generatePrivate( + new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); + } - // Load certificate - CertificateFactory certFactory = CertificateFactory.getInstance("X.509", - BouncyCastleProvider.PROVIDER_NAME); - if (isPEM(certFile.getBytes())) { - cert = (X509Certificate) certFactory - .generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); - } else { - cert = (X509Certificate) certFactory - .generateCertificate(new ByteArrayInputStream(certFile.getBytes())); - } - } - break; - } - } - PDSignature signature = new PDSignature(); - signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter - signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_SHA1); - signature.setName(name); - signature.setLocation(location); - signature.setReason(reason); - signature.setSignDate(Calendar.getInstance()); - - // Load the PDF - try (PDDocument document = PDDocument.load(pdf.getBytes())) { - logger.info("Successfully loaded the provided PDF"); - SignatureOptions signatureOptions = new SignatureOptions(); + // Load certificate + CertificateFactory certFactory = + CertificateFactory.getInstance( + "X.509", BouncyCastleProvider.PROVIDER_NAME); + if (isPEM(certFile.getBytes())) { + cert = + (X509Certificate) + certFactory.generateCertificate( + new ByteArrayInputStream( + parsePEM(certFile.getBytes()))); + } else { + cert = + (X509Certificate) + certFactory.generateCertificate( + new ByteArrayInputStream(certFile.getBytes())); + } + } + break; + } + } + PDSignature signature = new PDSignature(); + signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter + signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_SHA1); + signature.setName(name); + signature.setLocation(location); + signature.setReason(reason); + signature.setSignDate(Calendar.getInstance()); - // If you want to show the signature + // Load the PDF + try (PDDocument document = PDDocument.load(pdf.getBytes())) { + logger.info("Successfully loaded the provided PDF"); + SignatureOptions signatureOptions = new SignatureOptions(); - // ATTEMPT 2 - if (showSignature != null && showSignature) { - PDPage page = document.getPage(pageNumber - 1); + // If you want to show the signature - PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); - if (acroForm == null) { - acroForm = new PDAcroForm(document); - document.getDocumentCatalog().setAcroForm(acroForm); - } + // ATTEMPT 2 + if (showSignature != null && showSignature) { + PDPage page = document.getPage(pageNumber - 1); - // Create a new signature field and widget + PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); + if (acroForm == null) { + acroForm = new PDAcroForm(document); + document.getDocumentCatalog().setAcroForm(acroForm); + } - PDSignatureField signatureField = new PDSignatureField(acroForm); - PDAnnotationWidget widget = signatureField.getWidgets().get(0); - PDRectangle rect = new PDRectangle(100, 100, 200, 50); // Define the rectangle size here - widget.setRectangle(rect); - page.getAnnotations().add(widget); + // Create a new signature field and widget -// Set the appearance for the signature field - PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary(); - PDAppearanceStream appearanceStream = new PDAppearanceStream(document); - appearanceStream.setResources(new PDResources()); - appearanceStream.setBBox(rect); - appearanceDict.setNormalAppearance(appearanceStream); - widget.setAppearance(appearanceDict); + PDSignatureField signatureField = new PDSignatureField(acroForm); + PDAnnotationWidget widget = signatureField.getWidgets().get(0); + PDRectangle rect = + new PDRectangle(100, 100, 200, 50); // Define the rectangle size here + widget.setRectangle(rect); + page.getAnnotations().add(widget); - try (PDPageContentStream contentStream = new PDPageContentStream(document, appearanceStream)) { - contentStream.beginText(); - contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12); - contentStream.newLineAtOffset(110, 130); - contentStream.showText("Digitally signed by: " + (name != null ? name : "Unknown")); - contentStream.newLineAtOffset(0, -15); - contentStream.showText("Date: " + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z").format(new Date())); - contentStream.newLineAtOffset(0, -15); - if (reason != null && !reason.isEmpty()) { - contentStream.showText("Reason: " + reason); - contentStream.newLineAtOffset(0, -15); - } - if (location != null && !location.isEmpty()) { - contentStream.showText("Location: " + location); - contentStream.newLineAtOffset(0, -15); - } - contentStream.endText(); - } + // Set the appearance for the signature field + PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary(); + PDAppearanceStream appearanceStream = new PDAppearanceStream(document); + appearanceStream.setResources(new PDResources()); + appearanceStream.setBBox(rect); + appearanceDict.setNormalAppearance(appearanceStream); + widget.setAppearance(appearanceDict); - // Add the widget annotation to the page - page.getAnnotations().add(widget); + try (PDPageContentStream contentStream = + new PDPageContentStream(document, appearanceStream)) { + contentStream.beginText(); + contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12); + contentStream.newLineAtOffset(110, 130); + contentStream.showText( + "Digitally signed by: " + (name != null ? name : "Unknown")); + contentStream.newLineAtOffset(0, -15); + contentStream.showText( + "Date: " + + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z") + .format(new Date())); + contentStream.newLineAtOffset(0, -15); + if (reason != null && !reason.isEmpty()) { + contentStream.showText("Reason: " + reason); + contentStream.newLineAtOffset(0, -15); + } + if (location != null && !location.isEmpty()) { + contentStream.showText("Location: " + location); + contentStream.newLineAtOffset(0, -15); + } + contentStream.endText(); + } - // Add the signature field to the acroform - acroForm.getFields().add(signatureField); + // Add the widget annotation to the page + page.getAnnotations().add(widget); - // Handle multiple signatures by ensuring a unique field name - String baseFieldName = "Signature"; - String signatureFieldName = baseFieldName; - int suffix = 1; - while (acroForm.getField(signatureFieldName) != null) { - suffix++; - signatureFieldName = baseFieldName + suffix; - } - signatureField.setPartialName(signatureFieldName); - } - - document.addSignature(signature, signatureOptions); - logger.info("Signature added to the PDF document"); - // External signing - ExternalSigningSupport externalSigning = document - .saveIncrementalForExternalSigning(new ByteArrayOutputStream()); + // Add the signature field to the acroform + acroForm.getFields().add(signatureField); - byte[] content = IOUtils.toByteArray(externalSigning.getContent()); + // Handle multiple signatures by ensuring a unique field name + String baseFieldName = "Signature"; + String signatureFieldName = baseFieldName; + int suffix = 1; + while (acroForm.getField(signatureFieldName) != null) { + suffix++; + signatureFieldName = baseFieldName + suffix; + } + signatureField.setPartialName(signatureFieldName); + } - // Using BouncyCastle to sign - CMSTypedData cmsData = new CMSProcessableByteArray(content); + document.addSignature(signature, signatureOptions); + logger.info("Signature added to the PDF document"); + // External signing + ExternalSigningSupport externalSigning = + document.saveIncrementalForExternalSigning(new ByteArrayOutputStream()); - CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); - ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA") - .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey); + byte[] content = IOUtils.toByteArray(externalSigning.getContent()); - gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( - new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build()) - .build(signer, cert)); + // Using BouncyCastle to sign + CMSTypedData cmsData = new CMSProcessableByteArray(content); - gen.addCertificates(new JcaCertStore(Collections.singletonList(cert))); - CMSSignedData signedData = gen.generate(cmsData, false); + CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); + ContentSigner signer = + new JcaContentSignerBuilder("SHA256withRSA") + .setProvider(BouncyCastleProvider.PROVIDER_NAME) + .build(privateKey); - byte[] cmsSignature = signedData.getEncoded(); - logger.info("About to sign content using BouncyCastle"); - externalSigning.setSignature(cmsSignature); - logger.info("Signature set successfully"); + gen.addSignerInfoGenerator( + new JcaSignerInfoGeneratorBuilder( + new JcaDigestCalculatorProviderBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME) + .build()) + .build(signer, cert)); - // After setting the signature, return the resultant PDF - try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) { - document.save(signedPdfOutput); - return WebResponseUtils.boasToWebResponse(signedPdfOutput, - pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf"); + gen.addCertificates(new JcaCertStore(Collections.singletonList(cert))); + CMSSignedData signedData = gen.generate(cmsData, false); - } catch (Exception e) { - e.printStackTrace(); - } - } catch (Exception e) { - e.printStackTrace(); - } + byte[] cmsSignature = signedData.getEncoded(); + logger.info("About to sign content using BouncyCastle"); + externalSigning.setSignature(cmsSignature); + logger.info("Signature set successfully"); - return null; - } + // After setting the signature, return the resultant PDF + try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) { + document.save(signedPdfOutput); + return WebResponseUtils.boasToWebResponse( + signedPdfOutput, + pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf"); - private byte[] parsePEM(byte[] content) throws IOException { - PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); - return pemReader.readPemObject().getContent(); - } + } catch (Exception e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } - private boolean isPEM(byte[] content) { - String contentStr = new String(content); - return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); - } + return null; + } + private byte[] parsePEM(byte[] content) throws IOException { + PemReader pemReader = + new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); + return pemReader.readPemObject().getContent(); + } + + private boolean isPEM(byte[] content) { + String contentStr = new String(content); + return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 791dc736..f0bd8438 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -72,23 +72,22 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") public class GetInfoOnPDF { - - static ObjectMapper objectMapper = new ObjectMapper(); - @PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf") + static ObjectMapper objectMapper = new ObjectMapper(); + + @PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf") @Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO") - public ResponseEntity getPdfInfo(@ModelAttribute PDFFile request) - throws IOException { - MultipartFile inputFile = request.getFileInput(); - try ( - PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); - ) { + public ResponseEntity getPdfInfo(@ModelAttribute PDFFile request) throws IOException { + MultipartFile inputFile = request.getFileInput(); + try (PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); ) { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode jsonOutput = objectMapper.createObjectNode(); @@ -100,8 +99,7 @@ public class GetInfoOnPDF { ObjectNode compliancy = objectMapper.createObjectNode(); ObjectNode encryption = objectMapper.createObjectNode(); ObjectNode other = objectMapper.createObjectNode(); - - + metadata.put("Title", info.getTitle()); metadata.put("Author", info.getAuthor()); metadata.put("Subject", info.getSubject()); @@ -111,14 +109,11 @@ public class GetInfoOnPDF { metadata.put("CreationDate", formatDate(info.getCreationDate())); metadata.put("ModificationDate", formatDate(info.getModificationDate())); jsonOutput.set("Metadata", metadata); - - - - + // Total file size of the PDF long fileSizeInBytes = inputFile.getSize(); basicInfo.put("FileSizeInBytes", fileSizeInBytes); - + // Number of words, paragraphs, and images in the entire document String fullText = new PDFTextStripper().getText(pdfBoxDoc); String[] words = fullText.split("\\s+"); @@ -129,8 +124,7 @@ public class GetInfoOnPDF { // Number of characters in the entire document (including spaces and special characters) int charCount = fullText.length(); basicInfo.put("CharacterCount", charCount); - - + // Initialize the flags and types boolean hasCompression = false; String compressionType = "None"; @@ -147,26 +141,21 @@ public class GetInfoOnPDF { } } basicInfo.put("Compression", hasCompression); - if(hasCompression) - basicInfo.put("CompressionType", compressionType); - + if (hasCompression) basicInfo.put("CompressionType", compressionType); + String language = pdfBoxDoc.getDocumentCatalog().getLanguage(); basicInfo.put("Language", language); basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages()); - - + PDDocumentCatalog catalog = pdfBoxDoc.getDocumentCatalog(); String pageMode = catalog.getPageMode().name(); - + // Document Information using PDFBox docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); docInfoNode.put("Trapped", info.getTrapped()); - docInfoNode.put("Page Mode", getPageModeDescription(pageMode));; - + docInfoNode.put("Page Mode", getPageModeDescription(pageMode)); + ; - - - PDAcroForm acroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm(); ObjectNode formFieldsNode = objectMapper.createObjectNode(); @@ -177,41 +166,37 @@ public class GetInfoOnPDF { } jsonOutput.set("FormFields", formFieldsNode); - - - - - - //embeed files TODO size - if(catalog.getNames() != null) { - PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles(); - - ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); - if (efTree != null) { - Map efMap = efTree.getNames(); - if (efMap != null) { - for (Map.Entry entry : efMap.entrySet()) { - ObjectNode embeddedFileNode = objectMapper.createObjectNode(); - embeddedFileNode.put("Name", entry.getKey()); - PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile(); - if (embeddedFile != null) { - embeddedFileNode.put("FileSize", embeddedFile.getLength()); // size in bytes - } - embeddedFilesArray.add(embeddedFileNode); - } - } - } - other.set("EmbeddedFiles", embeddedFilesArray); + // embeed files TODO size + if (catalog.getNames() != null) { + PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles(); + + ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); + if (efTree != null) { + Map efMap = efTree.getNames(); + if (efMap != null) { + for (Map.Entry entry : + efMap.entrySet()) { + ObjectNode embeddedFileNode = objectMapper.createObjectNode(); + embeddedFileNode.put("Name", entry.getKey()); + PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile(); + if (embeddedFile != null) { + embeddedFileNode.put( + "FileSize", embeddedFile.getLength()); // size in bytes + } + embeddedFilesArray.add(embeddedFileNode); + } + } + } + other.set("EmbeddedFiles", embeddedFilesArray); } - - - //attachments TODO size + // attachments TODO size ArrayNode attachmentsArray = objectMapper.createArrayNode(); for (PDPage page : pdfBoxDoc.getPages()) { for (PDAnnotation annotation : page.getAnnotations()) { if (annotation instanceof PDAnnotationFileAttachment) { - PDAnnotationFileAttachment fileAttachmentAnnotation = (PDAnnotationFileAttachment) annotation; + PDAnnotationFileAttachment fileAttachmentAnnotation = + (PDAnnotationFileAttachment) annotation; ObjectNode attachmentNode = objectMapper.createObjectNode(); attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName()); @@ -223,7 +208,7 @@ public class GetInfoOnPDF { } other.set("Attachments", attachmentsArray); - //Javascript + // Javascript PDDocumentNameDictionary namesDict = catalog.getNames(); ArrayNode javascriptArray = objectMapper.createArrayNode(); @@ -254,9 +239,9 @@ public class GetInfoOnPDF { } other.set("JavaScript", javascriptArray); - - //TODO size - PDOptionalContentProperties ocProperties = pdfBoxDoc.getDocumentCatalog().getOCProperties(); + // TODO size + PDOptionalContentProperties ocProperties = + pdfBoxDoc.getDocumentCatalog().getOCProperties(); ArrayNode layersArray = objectMapper.createArrayNode(); if (ocProperties != null) { @@ -268,34 +253,38 @@ public class GetInfoOnPDF { } other.set("Layers", layersArray); - - //TODO Security - + // TODO Security - - - PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot(); + PDStructureTreeRoot structureTreeRoot = + pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot(); ArrayNode structureTreeArray; - try { - if(structureTreeRoot != null) { - structureTreeArray = exploreStructureTree(structureTreeRoot.getKids()); - other.set("StructureTree", structureTreeArray); - } - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - + try { + if (structureTreeRoot != null) { + structureTreeArray = exploreStructureTree(structureTreeRoot.getKids()); + other.set("StructureTree", structureTreeArray); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A"); boolean isPdfXCompliant = checkForStandard(pdfBoxDoc, "PDF/X"); boolean isPdfECompliant = checkForStandard(pdfBoxDoc, "PDF/E"); boolean isPdfVTCompliant = checkForStandard(pdfBoxDoc, "PDF/VT"); boolean isPdfUACompliant = checkForStandard(pdfBoxDoc, "PDF/UA"); - boolean isPdfBCompliant = checkForStandard(pdfBoxDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard. - boolean isPdfSECCompliant = checkForStandard(pdfBoxDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021. - + boolean isPdfBCompliant = + checkForStandard( + pdfBoxDoc, + "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't + // an official ISO standard. + boolean isPdfSECCompliant = + checkForStandard( + pdfBoxDoc, + "PDF/SEC"); // This might not be effective since PDF/SEC was under + // development in 2021. + compliancy.put("IsPDF/ACompliant", isPdfACompliant); compliancy.put("IsPDF/XCompliant", isPdfXCompliant); compliancy.put("IsPDF/ECompliant", isPdfECompliant); @@ -304,10 +293,6 @@ public class GetInfoOnPDF { compliancy.put("IsPDF/BCompliant", isPdfBCompliant); compliancy.put("IsPDF/SECCompliant", isPdfSECCompliant); - - - - PDOutlineNode root = pdfBoxDoc.getDocumentCatalog().getDocumentOutline(); ArrayNode bookmarksArray = objectMapper.createArrayNode(); @@ -318,33 +303,29 @@ public class GetInfoOnPDF { } other.set("Bookmarks/Outline/TOC", bookmarksArray); - - - PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata(); - - String xmpString = null; - - if (pdMetadata != null) { - try { - COSInputStream is = pdMetadata.createInputStream(); - DomXmpParser domXmpParser = new DomXmpParser(); - XMPMetadata xmpMeta = domXmpParser.parse(is); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - new XmpSerializer().serialize(xmpMeta, os, true); - xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8); - } catch (XmpParsingException | IOException e) { - e.printStackTrace(); - } - } - - other.put("XMPMetadata", xmpString); + PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata(); + + String xmpString = null; + + if (pdMetadata != null) { + try { + COSInputStream is = pdMetadata.createInputStream(); + DomXmpParser domXmpParser = new DomXmpParser(); + XMPMetadata xmpMeta = domXmpParser.parse(is); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmpMeta, os, true); + xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8); + } catch (XmpParsingException | IOException e) { + e.printStackTrace(); + } + } + + other.put("XMPMetadata", xmpString); - - if (pdfBoxDoc.isEncrypted()) { - encryption.put("IsEncrypted", true); + encryption.put("IsEncrypted", true); // Retrieve encryption details using getEncryption() PDEncryption pdfEncryption = pdfBoxDoc.getEncryption(); @@ -353,31 +334,30 @@ public class GetInfoOnPDF { AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission(); if (ap != null) { ObjectNode permissionsNode = objectMapper.createObjectNode(); - + permissionsNode.put("CanAssembleDocument", ap.canAssembleDocument()); permissionsNode.put("CanExtractContent", ap.canExtractContent()); - permissionsNode.put("CanExtractForAccessibility", ap.canExtractForAccessibility()); + permissionsNode.put( + "CanExtractForAccessibility", ap.canExtractForAccessibility()); permissionsNode.put("CanFillInForm", ap.canFillInForm()); permissionsNode.put("CanModify", ap.canModify()); permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations()); permissionsNode.put("CanPrint", ap.canPrint()); permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded()); - encryption.set("Permissions", permissionsNode); // set the node under "Permissions" - } + encryption.set( + "Permissions", permissionsNode); // set the node under "Permissions" + } // Add other encryption-related properties as needed } else { - encryption.put("IsEncrypted", false); + encryption.put("IsEncrypted", false); } - - - ObjectNode pageInfoParent = objectMapper.createObjectNode(); for (int pageNum = 0; pageNum < pdfBoxDoc.getNumberOfPages(); pageNum++) { ObjectNode pageInfo = objectMapper.createObjectNode(); - // Retrieve the page + // Retrieve the page PDPage page = pdfBoxDoc.getPage(pageNum); // Page-level Information @@ -387,20 +367,20 @@ public class GetInfoOnPDF { float height = mediaBox.getHeight(); ObjectNode sizeInfo = objectMapper.createObjectNode(); - + getDimensionInfo(sizeInfo, width, height); - + sizeInfo.put("Standard Page", getPageSize(width, height)); pageInfo.set("Size", sizeInfo); - + pageInfo.put("Rotation", page.getRotation()); pageInfo.put("Page Orientation", getPageOrientation(width, height)); - // Boxes pageInfo.put("MediaBox", mediaBox.toString()); - // Assuming the following boxes are defined for your document; if not, you may get null values. + // Assuming the following boxes are defined for your document; if not, you may get + // null values. PDRectangle cropBox = page.getCropBox(); pageInfo.put("CropBox", cropBox == null ? "Undefined" : cropBox.toString()); @@ -416,13 +396,13 @@ public class GetInfoOnPDF { // Content Extraction PDFTextStripper textStripper = new PDFTextStripper(); textStripper.setStartPage(pageNum + 1); - textStripper.setEndPage(pageNum +1); + textStripper.setEndPage(pageNum + 1); String pageText = textStripper.getText(pdfBoxDoc); - + pageInfo.put("Text Characters Count", pageText.length()); // // Annotations - + List annotations = page.getAnnotations(); int subtypeCount = 0; @@ -430,10 +410,10 @@ public class GetInfoOnPDF { for (PDAnnotation annotation : annotations) { if (annotation.getSubtype() != null) { - subtypeCount++; // Increase subtype count + subtypeCount++; // Increase subtype count } if (annotation.getContents() != null) { - contentsCount++; // Increase contents count + contentsCount++; // Increase contents count } } @@ -442,26 +422,25 @@ public class GetInfoOnPDF { annotationsObject.put("SubtypeCount", subtypeCount); annotationsObject.put("ContentsCount", contentsCount); pageInfo.set("Annotations", annotationsObject); - - - + // Images (simplified) // This part is non-trivial as images can be embedded in multiple ways in a PDF. // Here is a basic structure to recognize image XObjects on a page. ArrayNode imagesArray = objectMapper.createArrayNode(); PDResources resources = page.getResources(); - for (COSName name : resources.getXObjectNames()) { PDXObject xObject = resources.getXObject(name); if (xObject instanceof PDImageXObject) { PDImageXObject image = (PDImageXObject) xObject; - + ObjectNode imageNode = objectMapper.createObjectNode(); imageNode.put("Width", image.getWidth()); imageNode.put("Height", image.getHeight()); - if(image.getMetadata() != null && image.getMetadata().getFile() != null && image.getMetadata().getFile().getFile() != null) { - imageNode.put("Name", image.getMetadata().getFile().getFile()); + if (image.getMetadata() != null + && image.getMetadata().getFile() != null + && image.getMetadata().getFile().getFile() != null) { + imageNode.put("Name", image.getMetadata().getFile().getFile()); } if (image.getColorSpace() != null) { imageNode.put("ColorSpace", image.getColorSpace().getName()); @@ -472,10 +451,9 @@ public class GetInfoOnPDF { } pageInfo.set("Images", imagesArray); - // Links ArrayNode linksArray = objectMapper.createArrayNode(); - Set uniqueURIs = new HashSet<>(); // To store unique URIs + Set uniqueURIs = new HashSet<>(); // To store unique URIs for (PDAnnotation annotation : annotations) { if (annotation instanceof PDAnnotationLink) { @@ -483,7 +461,7 @@ public class GetInfoOnPDF { if (linkAnnotation.getAction() instanceof PDActionURI) { PDActionURI uriAction = (PDActionURI) linkAnnotation.getAction(); String uri = uriAction.getURI(); - uniqueURIs.add(uri); // Add to set to ensure uniqueness + uniqueURIs.add(uri); // Add to set to ensure uniqueness } } } @@ -495,8 +473,7 @@ public class GetInfoOnPDF { linksArray.add(linkNode); } pageInfo.set("Links", linksArray); - - + // Fonts ArrayNode fontsArray = objectMapper.createArrayNode(); Map uniqueFontsMap = new HashMap<>(); @@ -526,13 +503,13 @@ public class GetInfoOnPDF { fontNode.put("IsNonsymbolic", (flags & 32) != 0); fontNode.put("FontFamily", fontDescriptor.getFontFamily()); - // Font stretch and BBox are not directly available in PDFBox's API, so these are omitted for simplicity + // Font stretch and BBox are not directly available in PDFBox's API, so + // these are omitted for simplicity fontNode.put("FontWeight", fontDescriptor.getFontWeight()); } - // Create a unique key for this font node based on its attributes - String uniqueKey = fontNode.toString(); + String uniqueKey = fontNode.toString(); // Increment count if this font exists, or initialize it if new if (uniqueFontsMap.containsKey(uniqueKey)) { @@ -551,17 +528,7 @@ public class GetInfoOnPDF { } pageInfo.set("Fonts", fontsArray); - - - - - - - - - - - + // Access resources dictionary ArrayNode colorSpacesArray = objectMapper.createArrayNode(); @@ -572,7 +539,7 @@ public class GetInfoOnPDF { PDICCBased iccBased = (PDICCBased) colorSpace; PDStream iccData = iccBased.getPDStream(); byte[] iccBytes = iccData.toByteArray(); - + // TODO: Further decode and analyze the ICC data if needed ObjectNode iccProfileNode = objectMapper.createObjectNode(); iccProfileNode.put("ICC Profile Length", iccBytes.length); @@ -580,14 +547,14 @@ public class GetInfoOnPDF { } } pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray); - // Other XObjects - Map xObjectCountMap = new HashMap<>(); // To store the count for each type + Map xObjectCountMap = + new HashMap<>(); // To store the count for each type for (COSName name : resources.getXObjectNames()) { PDXObject xObject = resources.getXObject(name); String xObjectType; - + if (xObject instanceof PDImageXObject) { xObjectType = "Image"; } else if (xObject instanceof PDFormXObject) { @@ -597,7 +564,8 @@ public class GetInfoOnPDF { } // Increment the count for this type in the map - xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1); + xObjectCountMap.put( + xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1); } // Add the count map to pageInfo (or wherever you want to store it) @@ -606,14 +574,11 @@ public class GetInfoOnPDF { xObjectCountNode.put(entry.getKey(), entry.getValue()); } pageInfo.set("XObjectCounts", xObjectCountNode); - - - ArrayNode multimediaArray = objectMapper.createArrayNode(); for (PDAnnotation annotation : annotations) { - if ("RichMedia".equals(annotation.getSubtype())) { + if ("RichMedia".equals(annotation.getSubtype())) { ObjectNode multimediaNode = objectMapper.createObjectNode(); // Extract details from the annotation as needed multimediaArray.add(multimediaNode); @@ -622,32 +587,29 @@ public class GetInfoOnPDF { pageInfo.set("Multimedia", multimediaArray); - - - pageInfoParent.set("Page " + (pageNum+1), pageInfo); + pageInfoParent.set("Page " + (pageNum + 1), pageInfo); } - jsonOutput.set("BasicInfo", basicInfo); jsonOutput.set("DocumentInfo", docInfoNode); jsonOutput.set("Compliancy", compliancy); jsonOutput.set("Encryption", encryption); jsonOutput.set("Other", other); jsonOutput.set("PerPageInfo", pageInfoParent); - - - + // Save JSON to file - String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput); - - - - return WebResponseUtils.bytesToWebResponse(jsonString.getBytes(StandardCharsets.UTF_8), "response.json", MediaType.APPLICATION_JSON); - + String jsonString = + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput); + + return WebResponseUtils.bytesToWebResponse( + jsonString.getBytes(StandardCharsets.UTF_8), + "response.json", + MediaType.APPLICATION_JSON); + } catch (Exception e) { e.printStackTrace(); } - return null; + return null; } private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) { @@ -665,7 +627,7 @@ public class GetInfoOnPDF { } } - public String getPageOrientation(double width, double height) { + public String getPageOrientation(double width, double height) { if (width > height) { return "Landscape"; } else if (height > width) { @@ -674,6 +636,7 @@ public class GetInfoOnPDF { return "Square"; } } + public String getPageSize(float width, float height) { // Define standard page sizes Map standardSizes = new HashMap<>(); @@ -696,21 +659,22 @@ public class GetInfoOnPDF { return "Custom"; } - private boolean isCloseToSize(float width, float height, float standardWidth, float standardHeight) { + private boolean isCloseToSize( + float width, float height, float standardWidth, float standardHeight) { float tolerance = 1.0f; // You can adjust the tolerance as needed - return Math.abs(width - standardWidth) <= tolerance && Math.abs(height - standardHeight) <= tolerance; + return Math.abs(width - standardWidth) <= tolerance + && Math.abs(height - standardHeight) <= tolerance; } - - public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) { + public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) { float ppi = 72; // Points Per Inch - + float widthInInches = width / ppi; float heightInInches = height / ppi; - + float widthInCm = widthInInches * 2.54f; float heightInCm = heightInInches * 2.54f; - + dimensionInfo.put("Width (px)", String.format("%.2f", width)); dimensionInfo.put("Height (px)", String.format("%.2f", height)); dimensionInfo.put("Width (in)", String.format("%.2f", widthInInches)); @@ -720,33 +684,33 @@ public class GetInfoOnPDF { return dimensionInfo; } + public static boolean checkForStandard(PDDocument document, String standardKeyword) { + // Check XMP Metadata + try { + PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata(); + if (pdMetadata != null) { + COSInputStream metaStream = pdMetadata.createInputStream(); + DomXmpParser domXmpParser = new DomXmpParser(); + XMPMetadata xmpMeta = domXmpParser.parse(metaStream); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmpMeta, baos, true); + String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8); -public static boolean checkForStandard(PDDocument document, String standardKeyword) { - // Check XMP Metadata - try { - PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata(); - if (pdMetadata != null) { - COSInputStream metaStream = pdMetadata.createInputStream(); - DomXmpParser domXmpParser = new DomXmpParser(); - XMPMetadata xmpMeta = domXmpParser.parse(metaStream); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new XmpSerializer().serialize(xmpMeta, baos, true); - String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8); - - if (xmpString.contains(standardKeyword)) { - return true; + if (xmpString.contains(standardKeyword)) { + return true; + } } + } catch ( + Exception + e) { // Catching general exception for brevity, ideally you'd catch specific + // exceptions. + e.printStackTrace(); } - } catch (Exception e) { // Catching general exception for brevity, ideally you'd catch specific exceptions. - e.printStackTrace(); - } - - return false; -} - + return false; + } + public ArrayNode exploreStructureTree(List nodes) { ArrayNode elementsArray = objectMapper.createArrayNode(); if (nodes != null) { @@ -773,7 +737,6 @@ public static boolean checkForStandard(PDDocument document, String standardKeywo return elementsArray; } - public String getContent(PDStructureElement structureElement) { StringBuilder contentBuilder = new StringBuilder(); @@ -790,8 +753,7 @@ public static boolean checkForStandard(PDDocument document, String standardKeywo return contentBuilder.toString(); } - - + private String formatDate(Calendar calendar) { if (calendar != null) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index 639b6973..4c3a9517 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -16,9 +16,11 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.security.AddPasswordRequest; import stirling.software.SPDF.model.api.security.PDFPasswordRequest; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") @@ -26,29 +28,31 @@ public class PasswordController { private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); - @PostMapping(consumes = "multipart/form-data", value = "/remove-password") @Operation( - summary = "Remove password from a PDF file", - description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO" - ) - public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest request) throws IOException { + summary = "Remove password from a PDF file", + description = + "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO") + public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest request) + throws IOException { MultipartFile fileInput = request.getFileInput(); String password = request.getPassword(); - - - + PDDocument document = PDDocument.load(fileInput.getBytes(), password); document.setAllSecurityToBeRemoved(true); - return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf"); + return WebResponseUtils.pdfDocToWebResponse( + document, + fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_password_removed.pdf"); } @PostMapping(consumes = "multipart/form-data", value = "/add-password") @Operation( - summary = "Add password to a PDF file", - description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF" - ) - public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest request) throws IOException { + summary = "Add password to a PDF file", + description = + "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF") + public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest request) + throws IOException { MultipartFile fileInput = request.getFileInput(); String ownerPassword = request.getOwnerPassword(); String password = request.getPassword(); @@ -74,16 +78,19 @@ public class PasswordController { ap.setCanPrintFaithful(!canPrintFaithful); StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap); - if(!"".equals(ownerPassword) || !"".equals(password)) { - spp.setEncryptionKeyLength(keyLength); + if (!"".equals(ownerPassword) || !"".equals(password)) { + spp.setEncryptionKeyLength(keyLength); } spp.setPermissions(ap); document.protect(spp); - if("".equals(ownerPassword) && "".equals(password)) - return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_permissions.pdf"); - return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); + if ("".equals(ownerPassword) && "".equals(password)) + return WebResponseUtils.pdfDocToWebResponse( + document, + fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_permissions.pdf"); + return WebResponseUtils.pdfDocToWebResponse( + document, + fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); } - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java index 825544dc..79d15065 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java @@ -26,10 +26,12 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.PDFText; import stirling.software.SPDF.model.api.security.RedactPdfRequest; import stirling.software.SPDF.pdf.TextFinder; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController @RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") @@ -37,11 +39,13 @@ public class RedactController { private static final Logger logger = LoggerFactory.getLogger(RedactController.class); - @PostMapping(value = "/auto-redact", consumes = "multipart/form-data") - @Operation(summary = "Redacts listOfText in a PDF document", - description = "This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO") - public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request) throws Exception { + @Operation( + summary = "Redacts listOfText in a PDF document", + description = + "This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO") + public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request) + throws Exception { MultipartFile file = request.getFileInput(); String listOfTextString = request.getListOfText(); boolean useRegex = request.isUseRegex(); @@ -49,15 +53,15 @@ public class RedactController { String colorString = request.getRedactColor(); float customPadding = request.getCustomPadding(); boolean convertPDFToImage = request.isConvertPDFToImage(); - - System.out.println(listOfTextString); - String[] listOfText = listOfTextString.split("\n"); + + System.out.println(listOfTextString); + String[] listOfText = listOfTextString.split("\n"); byte[] bytes = file.getBytes(); PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes)); - + Color redactColor; try { - if (!colorString.startsWith("#")) { + if (!colorString.startsWith("#")) { colorString = "#" + colorString; } redactColor = Color.decode(colorString); @@ -66,18 +70,14 @@ public class RedactController { redactColor = Color.BLACK; } - - for (String text : listOfText) { - text = text.trim(); - System.out.println(text); - TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool); + text = text.trim(); + System.out.println(text); + TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool); List foundTexts = textFinder.getTextLocations(document); - redactFoundText(document, foundTexts, customPadding,redactColor); + redactFoundText(document, foundTexts, customPadding, redactColor); } - - - + if (convertPDFToImage) { PDDocument imageDocument = new PDDocument(); PDFRenderer pdfRenderer = new PDFRenderer(document); @@ -97,27 +97,33 @@ public class RedactController { ByteArrayOutputStream baos = new ByteArrayOutputStream(); document.save(baos); document.close(); - + byte[] pdfContent = baos.toByteArray(); - return WebResponseUtils.bytesToWebResponse(pdfContent, + return WebResponseUtils.bytesToWebResponse( + pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf"); } - - private void redactFoundText(PDDocument document, List blocks, float customPadding, Color redactColor) throws IOException { + private void redactFoundText( + PDDocument document, List blocks, float customPadding, Color redactColor) + throws IOException { var allPages = document.getDocumentCatalog().getPages(); for (PDFText block : blocks) { var page = allPages.get(block.getPageIndex()); - PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true); + PDPageContentStream contentStream = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true, true); contentStream.setNonStrokingColor(redactColor); float padding = (block.getY2() - block.getY1()) * 0.3f + customPadding; PDRectangle pageBox = page.getBBox(); - contentStream.addRect(block.getX1(), pageBox.getHeight() - block.getY1() - padding, block.getX2() - block.getX1(), block.getY2() - block.getY1() + 2 * padding); + contentStream.addRect( + block.getX1(), + pageBox.getHeight() - block.getY1() - padding, + block.getX2() - block.getX1(), + block.getY2() - block.getY1() + 2 * padding); contentStream.fill(); contentStream.close(); } } - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java b/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java index dab9d1d7..21a33529 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.api.security; + import java.io.IOException; import org.apache.pdfbox.cos.COSDictionary; @@ -28,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.security.SanitizePdfRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -36,59 +38,68 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Security", description = "Security APIs") public class SanitizeController { - @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") - @Operation(summary = "Sanitize a PDF file", - description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO") - public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest request) throws IOException { - MultipartFile inputFile = request.getFileInput(); - boolean removeJavaScript = request.isRemoveJavaScript(); - boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); - boolean removeMetadata = request.isRemoveMetadata(); - boolean removeLinks = request.isRemoveLinks(); - boolean removeFonts = request.isRemoveFonts(); + @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") + @Operation( + summary = "Sanitize a PDF file", + description = + "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO") + public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest request) + throws IOException { + MultipartFile inputFile = request.getFileInput(); + boolean removeJavaScript = request.isRemoveJavaScript(); + boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); + boolean removeMetadata = request.isRemoveMetadata(); + boolean removeLinks = request.isRemoveLinks(); + boolean removeFonts = request.isRemoveFonts(); - try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { - if (removeJavaScript) { - sanitizeJavaScript(document); - } + try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { + if (removeJavaScript) { + sanitizeJavaScript(document); + } - if (removeEmbeddedFiles) { - sanitizeEmbeddedFiles(document); - } + if (removeEmbeddedFiles) { + sanitizeEmbeddedFiles(document); + } - if (removeMetadata) { - sanitizeMetadata(document); - } + if (removeMetadata) { + sanitizeMetadata(document); + } - if (removeLinks) { - sanitizeLinks(document); - } + if (removeLinks) { + sanitizeLinks(document); + } - if (removeFonts) { - sanitizeFonts(document); - } + if (removeFonts) { + sanitizeFonts(document); + } - return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_sanitized.pdf"); - } - } - private void sanitizeJavaScript(PDDocument document) throws IOException { - // Get the root dictionary (catalog) of the PDF - PDDocumentCatalog catalog = document.getDocumentCatalog(); + return WebResponseUtils.pdfDocToWebResponse( + document, + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_sanitized.pdf"); + } + } - // Get the Names dictionary - COSDictionary namesDict = (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); + private void sanitizeJavaScript(PDDocument document) throws IOException { + // Get the root dictionary (catalog) of the PDF + PDDocumentCatalog catalog = document.getDocumentCatalog(); - if (namesDict != null) { - // Get the JavaScript dictionary - COSDictionary javaScriptDict = (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); + // Get the Names dictionary + COSDictionary namesDict = + (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); - if (javaScriptDict != null) { - // Remove the JavaScript dictionary - namesDict.removeItem(COSName.getPDFName("JavaScript")); - } - } - - for (PDPage page : document.getPages()) { + if (namesDict != null) { + // Get the JavaScript dictionary + COSDictionary javaScriptDict = + (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); + + if (javaScriptDict != null) { + // Remove the JavaScript dictionary + namesDict.removeItem(COSName.getPDFName("JavaScript")); + } + } + + for (PDPage page : document.getPages()) { for (PDAnnotation annotation : page.getAnnotations()) { if (annotation instanceof PDAnnotationWidget) { PDAnnotationWidget widget = (PDAnnotationWidget) annotation; @@ -96,33 +107,30 @@ public class SanitizeController { if (action instanceof PDActionJavaScript) { widget.setAction(null); } - } - } - PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); - if (acroForm != null) { - for (PDField field : acroForm.getFields()) { - PDFormFieldAdditionalActions actions = field.getActions(); - if(actions != null) { - if (actions.getC() instanceof PDActionJavaScript) { - actions.setC(null); - } - if (actions.getF() instanceof PDActionJavaScript) { - actions.setF(null); - } - if (actions.getK() instanceof PDActionJavaScript) { - actions.setK(null); - } - if (actions.getV() instanceof PDActionJavaScript) { - actions.setV(null); - } - } - } - } - } - } - - - + } + } + PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); + if (acroForm != null) { + for (PDField field : acroForm.getFields()) { + PDFormFieldAdditionalActions actions = field.getActions(); + if (actions != null) { + if (actions.getC() instanceof PDActionJavaScript) { + actions.setC(null); + } + if (actions.getF() instanceof PDActionJavaScript) { + actions.setF(null); + } + if (actions.getK() instanceof PDActionJavaScript) { + actions.setK(null); + } + if (actions.getV() instanceof PDActionJavaScript) { + actions.setV(null); + } + } + } + } + } + } private void sanitizeEmbeddedFiles(PDDocument document) { PDPageTree allPages = document.getPages(); @@ -134,7 +142,6 @@ public class SanitizeController { res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); } } - private void sanitizeMetadata(PDDocument document) { PDMetadata metadata = document.getDocumentCatalog().getMetadata(); @@ -143,8 +150,6 @@ public class SanitizeController { } } - - private void sanitizeLinks(PDDocument document) throws IOException { for (PDPage page : document.getPages()) { for (PDAnnotation annotation : page.getAnnotations()) { @@ -163,5 +168,4 @@ public class SanitizeController { page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); } } - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index b19636cd..daee68bf 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -30,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + import stirling.software.SPDF.model.api.security.AddWatermarkRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -38,154 +39,198 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Security", description = "Security APIs") public class WatermarkController { - @PostMapping(consumes = "multipart/form-data", value = "/add-watermark") - @Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO") - public ResponseEntity addWatermark(@ModelAttribute AddWatermarkRequest request) throws IOException, Exception { - MultipartFile pdfFile = request.getFileInput(); - String watermarkType = request.getWatermarkType(); - String watermarkText = request.getWatermarkText(); - MultipartFile watermarkImage = request.getWatermarkImage(); - String alphabet = request.getAlphabet(); - float fontSize = request.getFontSize(); - float rotation = request.getRotation(); - float opacity = request.getOpacity(); - int widthSpacer = request.getWidthSpacer(); - int heightSpacer = request.getHeightSpacer(); + @PostMapping(consumes = "multipart/form-data", value = "/add-watermark") + @Operation( + summary = "Add watermark to a PDF file", + description = + "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO") + public ResponseEntity addWatermark(@ModelAttribute AddWatermarkRequest request) + throws IOException, Exception { + MultipartFile pdfFile = request.getFileInput(); + String watermarkType = request.getWatermarkType(); + String watermarkText = request.getWatermarkText(); + MultipartFile watermarkImage = request.getWatermarkImage(); + String alphabet = request.getAlphabet(); + float fontSize = request.getFontSize(); + float rotation = request.getRotation(); + float opacity = request.getOpacity(); + int widthSpacer = request.getWidthSpacer(); + int heightSpacer = request.getHeightSpacer(); - // Load the input PDF - PDDocument document = PDDocument.load(pdfFile.getInputStream()); + // Load the input PDF + PDDocument document = PDDocument.load(pdfFile.getInputStream()); - // Create a page in the document - for (PDPage page : document.getPages()) { + // Create a page in the document + for (PDPage page : document.getPages()) { - // Get the page's content stream - PDPageContentStream contentStream = new PDPageContentStream(document, page, - PDPageContentStream.AppendMode.APPEND, true); + // Get the page's content stream + PDPageContentStream contentStream = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true); - // Set transparency - PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); - graphicsState.setNonStrokingAlphaConstant(opacity); - contentStream.setGraphicsStateParameters(graphicsState); + // Set transparency + PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); + graphicsState.setNonStrokingAlphaConstant(opacity); + contentStream.setGraphicsStateParameters(graphicsState); - if (watermarkType.equalsIgnoreCase("text")) { - addTextWatermark(contentStream, watermarkText, document, page, rotation, widthSpacer, heightSpacer, - fontSize, alphabet); - } else if (watermarkType.equalsIgnoreCase("image")) { - addImageWatermark(contentStream, watermarkImage, document, page, rotation, widthSpacer, heightSpacer, - fontSize); - } + if (watermarkType.equalsIgnoreCase("text")) { + addTextWatermark( + contentStream, + watermarkText, + document, + page, + rotation, + widthSpacer, + heightSpacer, + fontSize, + alphabet); + } else if (watermarkType.equalsIgnoreCase("image")) { + addImageWatermark( + contentStream, + watermarkImage, + document, + page, + rotation, + widthSpacer, + heightSpacer, + fontSize); + } - // Close the content stream - contentStream.close(); - } + // Close the content stream + contentStream.close(); + } - return WebResponseUtils.pdfDocToWebResponse(document, - pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); - } + return WebResponseUtils.pdfDocToWebResponse( + document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); + } - private void addTextWatermark(PDPageContentStream contentStream, String watermarkText, PDDocument document, - PDPage page, float rotation, int widthSpacer, int heightSpacer, float fontSize, String alphabet) throws IOException { - String resourceDir = ""; - PDFont font = PDType1Font.HELVETICA_BOLD; - switch (alphabet) { - case "arabic": - resourceDir = "static/fonts/NotoSansArabic-Regular.ttf"; - break; - case "japanese": - resourceDir = "static/fonts/Meiryo.ttf"; - break; - case "korean": - resourceDir = "static/fonts/malgun.ttf"; - break; - case "chinese": - resourceDir = "static/fonts/SimSun.ttf"; - break; - case "roman": - default: - resourceDir = "static/fonts/NotoSans-Regular.ttf"; - break; - } + private void addTextWatermark( + PDPageContentStream contentStream, + String watermarkText, + PDDocument document, + PDPage page, + float rotation, + int widthSpacer, + int heightSpacer, + float fontSize, + String alphabet) + throws IOException { + String resourceDir = ""; + PDFont font = PDType1Font.HELVETICA_BOLD; + switch (alphabet) { + case "arabic": + resourceDir = "static/fonts/NotoSansArabic-Regular.ttf"; + break; + case "japanese": + resourceDir = "static/fonts/Meiryo.ttf"; + break; + case "korean": + resourceDir = "static/fonts/malgun.ttf"; + break; + case "chinese": + resourceDir = "static/fonts/SimSun.ttf"; + break; + case "roman": + default: + resourceDir = "static/fonts/NotoSans-Regular.ttf"; + break; + } - - if(!resourceDir.equals("")) { + if (!resourceDir.equals("")) { ClassPathResource classPathResource = new ClassPathResource(resourceDir); String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(".")); File tempFile = File.createTempFile("NotoSansFont", fileExtension); - try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) { + try (InputStream is = classPathResource.getInputStream(); + FileOutputStream os = new FileOutputStream(tempFile)) { IOUtils.copy(is, os); } - + font = PDType0Font.load(document, tempFile); tempFile.deleteOnExit(); } - - contentStream.setFont(font, fontSize); - contentStream.setNonStrokingColor(Color.LIGHT_GRAY); - // Set size and location of text watermark - float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; - float watermarkHeight = heightSpacer + fontSize; - float pageWidth = page.getMediaBox().getWidth(); - float pageHeight = page.getMediaBox().getHeight(); - int watermarkRows = (int) (pageHeight / watermarkHeight + 1); - int watermarkCols = (int) (pageWidth / watermarkWidth + 1); + contentStream.setFont(font, fontSize); + contentStream.setNonStrokingColor(Color.LIGHT_GRAY); - // Add the text watermark - for (int i = 0; i < watermarkRows; i++) { - for (int j = 0; j < watermarkCols; j++) { - contentStream.beginText(); - contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), - j * watermarkWidth, i * watermarkHeight)); - contentStream.showText(watermarkText); - contentStream.endText(); - } - } - } + // Set size and location of text watermark + float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; + float watermarkHeight = heightSpacer + fontSize; + float pageWidth = page.getMediaBox().getWidth(); + float pageHeight = page.getMediaBox().getHeight(); + int watermarkRows = (int) (pageHeight / watermarkHeight + 1); + int watermarkCols = (int) (pageWidth / watermarkWidth + 1); - private void addImageWatermark(PDPageContentStream contentStream, MultipartFile watermarkImage, PDDocument document, PDPage page, float rotation, - int widthSpacer, int heightSpacer, float fontSize) throws IOException { + // Add the text watermark + for (int i = 0; i < watermarkRows; i++) { + for (int j = 0; j < watermarkCols; j++) { + contentStream.beginText(); + contentStream.setTextMatrix( + Matrix.getRotateInstance( + (float) Math.toRadians(rotation), + j * watermarkWidth, + i * watermarkHeight)); + contentStream.showText(watermarkText); + contentStream.endText(); + } + } + } -// Load the watermark image -BufferedImage image = ImageIO.read(watermarkImage.getInputStream()); + private void addImageWatermark( + PDPageContentStream contentStream, + MultipartFile watermarkImage, + PDDocument document, + PDPage page, + float rotation, + int widthSpacer, + int heightSpacer, + float fontSize) + throws IOException { -// Compute width based on original aspect ratio -float aspectRatio = (float) image.getWidth() / (float) image.getHeight(); + // Load the watermark image + BufferedImage image = ImageIO.read(watermarkImage.getInputStream()); -// Desired physical height (in PDF points) -float desiredPhysicalHeight = fontSize ; + // Compute width based on original aspect ratio + float aspectRatio = (float) image.getWidth() / (float) image.getHeight(); -// Desired physical width based on the aspect ratio -float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio; + // Desired physical height (in PDF points) + float desiredPhysicalHeight = fontSize; -// Convert the BufferedImage to PDImageXObject -PDImageXObject xobject = LosslessFactory.createFromImage(document, image); + // Desired physical width based on the aspect ratio + float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio; -// Calculate the number of rows and columns for watermarks -float pageWidth = page.getMediaBox().getWidth(); -float pageHeight = page.getMediaBox().getHeight(); -int watermarkRows = (int) ((pageHeight + heightSpacer) / (desiredPhysicalHeight + heightSpacer)); -int watermarkCols = (int) ((pageWidth + widthSpacer) / (desiredPhysicalWidth + widthSpacer)); + // Convert the BufferedImage to PDImageXObject + PDImageXObject xobject = LosslessFactory.createFromImage(document, image); -for (int i = 0; i < watermarkRows; i++) { -for (int j = 0; j < watermarkCols; j++) { -float x = j * (desiredPhysicalWidth + widthSpacer); -float y = i * (desiredPhysicalHeight + heightSpacer); + // Calculate the number of rows and columns for watermarks + float pageWidth = page.getMediaBox().getWidth(); + float pageHeight = page.getMediaBox().getHeight(); + int watermarkRows = + (int) ((pageHeight + heightSpacer) / (desiredPhysicalHeight + heightSpacer)); + int watermarkCols = + (int) ((pageWidth + widthSpacer) / (desiredPhysicalWidth + widthSpacer)); -// Save the graphics state -contentStream.saveGraphicsState(); + for (int i = 0; i < watermarkRows; i++) { + for (int j = 0; j < watermarkCols; j++) { + float x = j * (desiredPhysicalWidth + widthSpacer); + float y = i * (desiredPhysicalHeight + heightSpacer); -// Create rotation matrix and rotate -contentStream.transform(Matrix.getTranslateInstance(x + desiredPhysicalWidth / 2, y + desiredPhysicalHeight / 2)); -contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0)); -contentStream.transform(Matrix.getTranslateInstance(-desiredPhysicalWidth / 2, -desiredPhysicalHeight / 2)); - -// Draw the image and restore the graphics state -contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight); -contentStream.restoreGraphicsState(); -} - -} - - } + // Save the graphics state + contentStream.saveGraphicsState(); + // Create rotation matrix and rotate + contentStream.transform( + Matrix.getTranslateInstance( + x + desiredPhysicalWidth / 2, y + desiredPhysicalHeight / 2)); + contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0)); + contentStream.transform( + Matrix.getTranslateInstance( + -desiredPhysicalWidth / 2, -desiredPhysicalHeight / 2)); + + // Draw the image and restore the graphics state + contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight); + contentStream.restoreGraphicsState(); + } + } + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/strippers/PDFTableStripper.java b/src/main/java/stirling/software/SPDF/controller/api/strippers/PDFTableStripper.java index 5330c3ad..e2ed3ca0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/strippers/PDFTableStripper.java +++ b/src/main/java/stirling/software/SPDF/controller/api/strippers/PDFTableStripper.java @@ -24,91 +24,79 @@ import org.apache.pdfbox.text.PDFTextStripperByArea; import org.apache.pdfbox.text.TextPosition; /** + * Class to extract tabular data from a PDF. Works by making a first pass of the page to group all + * nearby text items together, and then inferring a 2D grid from these regions. Each table cell is + * then extracted using a PDFTextStripperByArea object. * - * Class to extract tabular data from a PDF. - * Works by making a first pass of the page to group all nearby text items - * together, and then inferring a 2D grid from these regions. Each table cell - * is then extracted using a PDFTextStripperByArea object. + *

Works best when headers are included in the detected region, to ensure representative text in + * every column. * - * Works best when - * headers are included in the detected region, to ensure representative text - * in every column. - * - * Based upon DrawPrintTextLocations PDFBox example + *

Based upon DrawPrintTextLocations PDFBox example * (https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/util/DrawPrintTextLocations.java) * * @author Beldaz */ -public class PDFTableStripper extends PDFTextStripper -{ +public class PDFTableStripper extends PDFTextStripper { /** * This will print the documents data, for each table cell. * * @param args The command line arguments. - * * @throws IOException If there is an error parsing the document. */ /* * Used in methods derived from DrawPrintTextLocations */ private AffineTransform flipAT; + private AffineTransform rotateAT; - /** - * Regions updated by calls to writeString - */ + /** Regions updated by calls to writeString */ private Set boxes; // Border to allow when finding intersections private double dx = 1.0; // This value works for me, feel free to tweak (or add setter) private double dy = 0.000; // Rows of text tend to overlap, so need to extend - /** - * Region in which to find table (otherwise whole page) - */ + /** Region in which to find table (otherwise whole page) */ private Rectangle2D regionArea; - /** - * Number of rows in inferred table - */ - private int nRows=0; + /** Number of rows in inferred table */ + private int nRows = 0; - /** - * Number of columns in inferred table - */ - private int nCols=0; + /** Number of columns in inferred table */ + private int nCols = 0; - /** - * This is the object that does the text extraction - */ + /** This is the object that does the text extraction */ private PDFTextStripperByArea regionStripper; /** * 1D intervals - used for calculateTableRegions() - * @author Beldaz * + * @author Beldaz */ public static class Interval { double start; double end; + public Interval(double start, double end) { - this.start=start; this.end = end; + this.start = start; + this.end = end; } + public void add(Interval col) { - if(col.startend) - end = col.end; + if (col.start < start) start = col.start; + if (col.end > end) end = col.end; } + public static void addTo(Interval x, LinkedList columns) { int p = 0; Iterator it = columns.iterator(); // Find where x should go - while(it.hasNext()) { + while (it.hasNext()) { Interval col = it.next(); - if(x.end>=col.start) { - if(x.start<=col.end) { // overlaps + if (x.end >= col.start) { + if (x.start <= col.end) { // overlaps x.add(col); it.remove(); } @@ -116,30 +104,26 @@ public class PDFTableStripper extends PDFTextStripper } ++p; } - while(it.hasNext()) { + while (it.hasNext()) { Interval col = it.next(); - if(x.start>col.end) - break; + if (x.start > col.end) break; x.add(col); it.remove(); } columns.add(p, x); } - } - /** * Instantiate a new PDFTableStripper object. * * @param document * @throws IOException If there is an error loading the properties. */ - public PDFTableStripper() throws IOException - { + public PDFTableStripper() throws IOException { super.setShouldSeparateByBeads(false); regionStripper = new PDFTextStripperByArea(); - regionStripper.setSortByPosition( true ); + regionStripper.setSortByPosition(true); } /** @@ -147,18 +131,15 @@ public class PDFTableStripper extends PDFTextStripper * * @param rect The rectangle area to retrieve the text from. */ - public void setRegion(Rectangle2D rect ) - { + public void setRegion(Rectangle2D rect) { regionArea = rect; } - public int getRows() - { + public int getRows() { return nRows; } - public int getColumns() - { + public int getColumns() { return nCols; } @@ -167,13 +148,11 @@ public class PDFTableStripper extends PDFTextStripper * * @return The text that was identified in that region. */ - public String getText(int row, int col) - { - return regionStripper.getTextForRegion("el"+col+"x"+row); + public String getText(int row, int col) { + return regionStripper.getTextForRegion("el" + col + "x" + row); } - public void extractTable(PDPage pdPage) throws IOException - { + public void extractTable(PDPage pdPage) throws IOException { setStartPage(getCurrentPageNo()); setEndPage(getCurrentPageNo()); @@ -186,11 +165,9 @@ public class PDFTableStripper extends PDFTextStripper // page may be rotated rotateAT = new AffineTransform(); int rotation = pdPage.getRotation(); - if (rotation != 0) - { + if (rotation != 0) { PDRectangle mediaBox = pdPage.getMediaBox(); - switch (rotation) - { + switch (rotation) { case 90: rotateAT.translate(mediaBox.getHeight(), 0); break; @@ -213,11 +190,12 @@ public class PDFTableStripper extends PDFTextStripper Rectangle2D[][] regions = calculateTableRegions(); -// System.err.println("Drawing " + nCols + "x" + nRows + "="+ nRows*nCols + " regions"); - for(int i=0; i columns = new LinkedList(); LinkedList rows = new LinkedList(); - for(Rectangle2D box: boxes) { + for (Rectangle2D box : boxes) { Interval x = new Interval(box.getMinX(), box.getMaxX()); Interval y = new Interval(box.getMinY(), box.getMaxY()); @@ -249,12 +227,17 @@ public class PDFTableStripper extends PDFTextStripper nRows = rows.size(); nCols = columns.size(); Rectangle2D[][] regions = new Rectangle2D[nCols][nRows]; - int i=0; + int i = 0; // Label regions from top left, rather than the transformed orientation - for(Interval column: columns) { - int j=0; - for(Interval row: rows) { - regions[nCols-i-1][nRows-j-1] = new Rectangle2D.Double(column.start, row.start, column.end - column.start, row.end - row.start); + for (Interval column : columns) { + int j = 0; + for (Interval row : rows) { + regions[nCols - i - 1][nRows - j - 1] = + new Rectangle2D.Double( + column.start, + row.start, + column.end - column.start, + row.end - row.start); ++j; } ++i; @@ -264,18 +247,15 @@ public class PDFTableStripper extends PDFTextStripper } /** - * Register each character's bounding box, updating boxes field to maintain - * a list of all distinct groups of characters. + * Register each character's bounding box, updating boxes field to maintain a list of all + * distinct groups of characters. * - * Overrides the default functionality of PDFTextStripper. - * Most of this is taken from DrawPrintTextLocations.java, with extra steps - * at end of main loop + *

Overrides the default functionality of PDFTextStripper. Most of this is taken from + * DrawPrintTextLocations.java, with extra steps at end of main loop */ @Override - protected void writeString(String string, List textPositions) throws IOException - { - for (TextPosition text : textPositions) - { + protected void writeString(String string, List textPositions) throws IOException { + for (TextPosition text : textPositions) { // glyph space -> user space // note: text.getTextMatrix() is *not* the Text Matrix, it's the Text Rendering Matrix AffineTransform at = text.getTextMatrix().createAffineTransform(); @@ -283,37 +263,35 @@ public class PDFTableStripper extends PDFTextStripper BoundingBox bbox = font.getBoundingBox(); // advance width, bbox height (glyph space) - float xadvance = font.getWidth(text.getCharacterCodes()[0]); // todo: should iterate all chars - Rectangle2D.Float rect = new Rectangle2D.Float(0, bbox.getLowerLeftY(), xadvance, bbox.getHeight()); + float xadvance = + font.getWidth(text.getCharacterCodes()[0]); // todo: should iterate all chars + Rectangle2D.Float rect = + new Rectangle2D.Float(0, bbox.getLowerLeftY(), xadvance, bbox.getHeight()); - if (font instanceof PDType3Font) - { + if (font instanceof PDType3Font) { // bbox and font matrix are unscaled at.concatenate(font.getFontMatrix().createAffineTransform()); - } - else - { + } else { // bbox and font matrix are already scaled to 1000 - at.scale(1/1000f, 1/1000f); + at.scale(1 / 1000f, 1 / 1000f); } Shape s = at.createTransformedShape(rect); s = flipAT.createTransformedShape(s); s = rotateAT.createTransformedShape(s); - // // Merge character's bounding box with boxes field // Rectangle2D bounds = s.getBounds2D(); // Pad sides to detect almost touching boxes Rectangle2D hitbox = bounds.getBounds2D(); - hitbox.add(bounds.getMinX() - dx , bounds.getMinY() - dy); - hitbox.add(bounds.getMaxX() + dx , bounds.getMaxY() + dy); + hitbox.add(bounds.getMinX() - dx, bounds.getMinY() - dy); + hitbox.add(bounds.getMaxX() + dx, bounds.getMaxY() + dy); // Find all overlapping boxes List intersectList = new ArrayList(); - for(Rectangle2D box: boxes) { - if(box.intersects(hitbox)) { + for (Rectangle2D box : boxes) { + if (box.intersects(hitbox)) { intersectList.add(box); } } @@ -321,38 +299,30 @@ public class PDFTableStripper extends PDFTextStripper // Combine all touching boxes and update // (NOTE: Potentially this could leave some overlapping boxes un-merged, // but it's sufficient for now and get's fixed up in calculateTableRegions) - for(Rectangle2D box: intersectList) { + for (Rectangle2D box : intersectList) { bounds.add(box); boxes.remove(box); } boxes.add(bounds); - } - } /** - * This method does nothing in this derived class, because beads and regions are incompatible. Beads are - * ignored when stripping by area. + * This method does nothing in this derived class, because beads and regions are incompatible. + * Beads are ignored when stripping by area. * * @param aShouldSeparateByBeads The new grouping of beads. */ @Override - public final void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) - { - } + public final void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) {} - /** - * Adapted from PDFTextStripperByArea - * {@inheritDoc} - */ + /** Adapted from PDFTextStripperByArea {@inheritDoc} */ @Override - protected void processTextPosition( TextPosition text ) - { - if(regionArea!=null && !regionArea.contains( text.getX(), text.getY() ) ) { + protected void processTextPosition(TextPosition text) { + if (regionArea != null && !regionArea.contains(text.getX(), text.getY())) { // skip character } else { - super.processTextPosition( text ); + super.processTextPosition(text); } } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 2c51e7d8..614dd8a0 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.web; + import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -15,138 +16,140 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.tags.Tag; + import jakarta.servlet.http.HttpServletRequest; import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.User; import stirling.software.SPDF.repository.UserRepository; + @Controller @Tag(name = "Account Security", description = "Account Security APIs") public class AccountWebController { - - @GetMapping("/login") - public String login(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication != null && authentication.isAuthenticated()) { + @GetMapping("/login") + public String login(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication != null && authentication.isAuthenticated()) { return "redirect:/"; } - - if (request.getParameter("error") != null) { - model.addAttribute("error", request.getParameter("error")); - } - if (request.getParameter("logout") != null) { + if (request.getParameter("error") != null) { - model.addAttribute("logoutMessage", "You have been logged out."); - } - - return "login"; - } - @Autowired - private UserRepository userRepository; // Assuming you have a repository for user operations + model.addAttribute("error", request.getParameter("error")); + } + if (request.getParameter("logout") != null) { + model.addAttribute("logoutMessage", "You have been logged out."); + } - @PreAuthorize("hasRole('ROLE_ADMIN')") - @GetMapping("/addUsers") - public String showAddUserForm(Model model, Authentication authentication) { - List allUsers = userRepository.findAll(); - Iterator iterator = allUsers.iterator(); + return "login"; + } - while(iterator.hasNext()) { - User user = iterator.next(); - if(user != null) { - for (Authority authority : user.getAuthorities()) { - if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { - iterator.remove(); - break; // Break out of the inner loop once the user is removed - } - } - } - } + @Autowired + private UserRepository userRepository; // Assuming you have a repository for user operations - model.addAttribute("users", allUsers); - model.addAttribute("currentUsername", authentication.getName()); - return "addUsers"; - } + @PreAuthorize("hasRole('ROLE_ADMIN')") + @GetMapping("/addUsers") + public String showAddUserForm(Model model, Authentication authentication) { + List allUsers = userRepository.findAll(); + Iterator iterator = allUsers.iterator(); - - @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") - @GetMapping("/account") - public String account(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { + while (iterator.hasNext()) { + User user = iterator.next(); + if (user != null) { + for (Authority authority : user.getAuthorities()) { + if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { + iterator.remove(); + break; // Break out of the inner loop once the user is removed + } + } + } + } + + model.addAttribute("users", allUsers); + model.addAttribute("currentUsername", authentication.getName()); + return "addUsers"; + } + + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") + @GetMapping("/account") + public String account(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { return "redirect:/"; } - if (authentication != null && authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; + if (principal instanceof UserDetails) { + // Cast the principal object to UserDetails + UserDetails userDetails = (UserDetails) principal; - // Retrieve username and other attributes - String username = userDetails.getUsername(); + // Retrieve username and other attributes + String username = userDetails.getUsername(); - // Fetch user details from the database - Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists - if (!user.isPresent()) { - // Handle error appropriately - return "redirect:/error"; // Example redirection in case of error - } + // Fetch user details from the database + Optional user = + userRepository.findByUsername( + username); // Assuming findByUsername method exists + if (!user.isPresent()) { + // Handle error appropriately + return "redirect:/error"; // Example redirection in case of error + } - // Convert settings map to JSON string - ObjectMapper objectMapper = new ObjectMapper(); - String settingsJson; - try { - settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); - } catch (JsonProcessingException e) { - // Handle JSON conversion error - e.printStackTrace(); - return "redirect:/error"; // Example redirection in case of error - } + // Convert settings map to JSON string + ObjectMapper objectMapper = new ObjectMapper(); + String settingsJson; + try { + settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); + } catch (JsonProcessingException e) { + // Handle JSON conversion error + e.printStackTrace(); + return "redirect:/error"; // Example redirection in case of error + } - // Add attributes to the model - model.addAttribute("username", username); - model.addAttribute("role", user.get().getRolesAsString()); - model.addAttribute("settings", settingsJson); - model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); - } - } else { - return "redirect:/"; - } - return "account"; - } - - - @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") - @GetMapping("/change-creds") - public String changeCreds(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { + // Add attributes to the model + model.addAttribute("username", username); + model.addAttribute("role", user.get().getRolesAsString()); + model.addAttribute("settings", settingsJson); + model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); + } + } else { return "redirect:/"; } - if (authentication != null && authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); + return "account"; + } - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") + @GetMapping("/change-creds") + public String changeCreds( + HttpServletRequest request, Model model, Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + return "redirect:/"; + } + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); - // Retrieve username and other attributes - String username = userDetails.getUsername(); + if (principal instanceof UserDetails) { + // Cast the principal object to UserDetails + UserDetails userDetails = (UserDetails) principal; - // Fetch user details from the database - Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists - if (!user.isPresent()) { - // Handle error appropriately - return "redirect:/error"; // Example redirection in case of error - } - // Add attributes to the model - model.addAttribute("username", username); - } - } else { - return "redirect:/"; - } - return "change-creds"; - } - - + // Retrieve username and other attributes + String username = userDetails.getUsername(); + + // Fetch user details from the database + Optional user = + userRepository.findByUsername( + username); // Assuming findByUsername method exists + if (!user.isPresent()) { + // Handle error appropriately + return "redirect:/error"; // Example redirection in case of error + } + // Add attributes to the model + model.addAttribute("username", username); + } + } else { + return "redirect:/"; + } + return "change-creds"; + } } diff --git a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java index 3bad71c9..b34bac3c 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java @@ -25,14 +25,14 @@ public class ConverterWebController { model.addAttribute("currentPage", "html-to-pdf"); return "convert/html-to-pdf"; } + @GetMapping("/markdown-to-pdf") @Hidden public String convertMarkdownToPdfForm(Model model) { model.addAttribute("currentPage", "markdown-to-pdf"); return "convert/markdown-to-pdf"; } - - + @GetMapping("/url-to-pdf") @Hidden public String convertURLToPdfForm(Model model) { @@ -40,25 +40,22 @@ public class ConverterWebController { return "convert/url-to-pdf"; } - @GetMapping("/pdf-to-img") @Hidden public String pdfToimgForm(Model model) { model.addAttribute("currentPage", "pdf-to-img"); return "convert/pdf-to-img"; } - + @GetMapping("/file-to-pdf") @Hidden public String convertToPdfForm(Model model) { model.addAttribute("currentPage", "file-to-pdf"); return "convert/file-to-pdf"; } - - - //PDF TO...... - + // PDF TO...... + @GetMapping("/pdf-to-html") @Hidden public ModelAndView pdfToHTML() { @@ -107,7 +104,6 @@ public class ConverterWebController { return modelAndView; } - @GetMapping("/pdf-to-pdfa") @Hidden public String pdfToPdfAForm(Model model) { diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index b670a129..7b6489d6 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -32,70 +32,63 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Controller @Tag(name = "General", description = "General APIs") public class GeneralWebController { - - - - @GetMapping("/pipeline") - @Hidden - public String pipelineForm(Model model) { - model.addAttribute("currentPage", "pipeline"); + @GetMapping("/pipeline") + @Hidden + public String pipelineForm(Model model) { + model.addAttribute("currentPage", "pipeline"); - List pipelineConfigs = new ArrayList<>(); - List> pipelineConfigsWithNames = new ArrayList<>(); - - if(new File("./pipeline/defaultWebUIConfigs/").exists()) { - try (Stream paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) { - List jsonFiles = paths - .filter(Files::isRegularFile) - .filter(p -> p.toString().endsWith(".json")) - .collect(Collectors.toList()); - - for (Path jsonFile : jsonFiles) { - String content = Files.readString(jsonFile, StandardCharsets.UTF_8); - pipelineConfigs.add(content); - } - - for (String config : pipelineConfigs) { - Map jsonContent = new ObjectMapper().readValue(config, new TypeReference>(){}); - - String name = (String) jsonContent.get("name"); - Map configWithName = new HashMap<>(); - configWithName.put("json", config); - configWithName.put("name", name); - pipelineConfigsWithNames.add(configWithName); - } - - - - - } catch (IOException e) { - e.printStackTrace(); - } - } - if(pipelineConfigsWithNames.size() == 0) { - Map configWithName = new HashMap<>(); - configWithName.put("json", ""); - configWithName.put("name", "No preloaded configs found"); - pipelineConfigsWithNames.add(configWithName); - } - model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames); + List pipelineConfigs = new ArrayList<>(); + List> pipelineConfigsWithNames = new ArrayList<>(); - model.addAttribute("pipelineConfigs", pipelineConfigs); + if (new File("./pipeline/defaultWebUIConfigs/").exists()) { + try (Stream paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) { + List jsonFiles = + paths.filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".json")) + .collect(Collectors.toList()); - return "pipeline"; - } + for (Path jsonFile : jsonFiles) { + String content = Files.readString(jsonFile, StandardCharsets.UTF_8); + pipelineConfigs.add(content); + } + + for (String config : pipelineConfigs) { + Map jsonContent = + new ObjectMapper() + .readValue(config, new TypeReference>() {}); + + String name = (String) jsonContent.get("name"); + Map configWithName = new HashMap<>(); + configWithName.put("json", config); + configWithName.put("name", name); + pipelineConfigsWithNames.add(configWithName); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + if (pipelineConfigsWithNames.size() == 0) { + Map configWithName = new HashMap<>(); + configWithName.put("json", ""); + configWithName.put("name", "No preloaded configs found"); + pipelineConfigsWithNames.add(configWithName); + } + model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames); + + model.addAttribute("pipelineConfigs", pipelineConfigs); + + return "pipeline"; + } - - - @GetMapping("/merge-pdfs") @Hidden public String mergePdfForm(Model model) { model.addAttribute("currentPage", "merge-pdfs"); return "merge-pdfs"; } - + @GetMapping("/split-pdf-by-sections") @Hidden public String splitPdfBySections(Model model) { @@ -109,57 +102,56 @@ public class GeneralWebController { model.addAttribute("currentPage", "view-pdf"); return "view-pdf"; } - + @GetMapping("/multi-tool") @Hidden public String multiToolForm(Model model) { model.addAttribute("currentPage", "multi-tool"); return "multi-tool"; } - - + @GetMapping("/remove-pages") @Hidden public String pageDeleter(Model model) { model.addAttribute("currentPage", "remove-pages"); return "remove-pages"; } - + @GetMapping("/pdf-organizer") @Hidden public String pageOrganizer(Model model) { model.addAttribute("currentPage", "pdf-organizer"); return "pdf-organizer"; } - + @GetMapping("/extract-page") @Hidden public String extractPages(Model model) { model.addAttribute("currentPage", "extract-page"); return "extract-page"; } - + @GetMapping("/pdf-to-single-page") @Hidden public String pdfToSinglePage(Model model) { model.addAttribute("currentPage", "pdf-to-single-page"); return "pdf-to-single-page"; } - + @GetMapping("/rotate-pdf") @Hidden public String rotatePdfForm(Model model) { model.addAttribute("currentPage", "rotate-pdf"); return "rotate-pdf"; } - + @GetMapping("/split-pdfs") @Hidden public String splitPdfForm(Model model) { model.addAttribute("currentPage", "split-pdfs"); return "split-pdfs"; } - + @GetMapping("/sign") @Hidden public String signForm(Model model) { @@ -167,22 +159,20 @@ public class GeneralWebController { model.addAttribute("fonts", getFontNames()); return "sign"; } - + @GetMapping("/multi-page-layout") @Hidden public String multiPageLayoutForm(Model model) { model.addAttribute("currentPage", "multi-page-layout"); return "multi-page-layout"; } - - + @GetMapping("/scale-pages") @Hidden public String scalePagesFrom(Model model) { model.addAttribute("currentPage", "scale-pages"); return "scale-pages"; } - @GetMapping("/split-by-size-or-count") @Hidden @@ -190,18 +180,16 @@ public class GeneralWebController { model.addAttribute("currentPage", "split-by-size-or-count"); return "split-by-size-or-count"; } - + @GetMapping("/overlay-pdf") @Hidden public String overlayPdf(Model model) { model.addAttribute("currentPage", "overlay-pdf"); return "overlay-pdf"; } - - - @Autowired - private ResourceLoader resourceLoader; - + + @Autowired private ResourceLoader resourceLoader; + private List getFontNames() { List fontNames = new ArrayList<>(); @@ -216,25 +204,27 @@ public class GeneralWebController { private List getFontNamesFromLocation(String locationPattern) { try { - Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader) - .getResources(locationPattern); + Resource[] resources = + ResourcePatternUtils.getResourcePatternResolver(resourceLoader) + .getResources(locationPattern); return Arrays.stream(resources) - .map(resource -> { - try { - String filename = resource.getFilename(); - if (filename != null) { - int lastDotIndex = filename.lastIndexOf('.'); - if (lastDotIndex != -1) { - String name = filename.substring(0, lastDotIndex); - String extension = filename.substring(lastDotIndex + 1); - return new FontResource(name, extension); + .map( + resource -> { + try { + String filename = resource.getFilename(); + if (filename != null) { + int lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex != -1) { + String name = filename.substring(0, lastDotIndex); + String extension = filename.substring(lastDotIndex + 1); + return new FontResource(name, extension); + } + } + return null; + } catch (Exception e) { + throw new RuntimeException("Error processing filename", e); } - } - return null; - } catch (Exception e) { - throw new RuntimeException("Error processing filename", e); - } - }) + }) .filter(Objects::nonNull) .collect(Collectors.toList()); } catch (Exception e) { @@ -242,64 +232,65 @@ public class GeneralWebController { } } - public String getFormatFromExtension(String extension) { switch (extension) { - case "ttf": return "truetype"; - case "woff": return "woff"; - case "woff2": return "woff2"; - case "eot": return "embedded-opentype"; - case "svg": return "svg"; - default: return ""; // or throw an exception if an unexpected extension is encountered + case "ttf": + return "truetype"; + case "woff": + return "woff"; + case "woff2": + return "woff2"; + case "eot": + return "embedded-opentype"; + case "svg": + return "svg"; + default: + return ""; // or throw an exception if an unexpected extension is encountered } } - public class FontResource { private String name; private String extension; private String type; + public FontResource(String name, String extension) { this.name = name; this.extension = extension; this.type = getFormatFromExtension(extension); } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getExtension() { - return extension; - } + public String getExtension() { + return extension; + } - public void setExtension(String extension) { - this.extension = extension; - } + public void setExtension(String extension) { + this.extension = extension; + } - public String getType() { - return type; - } + public String getType() { + return type; + } - public void setType(String type) { - this.type = type; - } - - + public void setType(String type) { + this.type = type; + } } - @GetMapping("/crop") @Hidden public String cropForm(Model model) { model.addAttribute("currentPage", "crop"); return "crop"; } - @GetMapping("/auto-split-pdf") @Hidden diff --git a/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java b/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java index b6d08fbf..23c19f9a 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java @@ -8,20 +8,19 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import io.swagger.v3.oas.annotations.Hidden; + import stirling.software.SPDF.model.ApplicationProperties; @Controller public class HomeWebController { - + @GetMapping("/about") @Hidden public String gameForm(Model model) { model.addAttribute("currentPage", "about"); return "about"; } - - - + @GetMapping("/") public String home(Model model) { model.addAttribute("currentPage", "home"); @@ -32,21 +31,18 @@ public class HomeWebController { public String root(Model model) { return "redirect:/"; } - - @Autowired - ApplicationProperties applicationProperties; + @Autowired ApplicationProperties applicationProperties; @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody @Hidden public String getRobotsTxt() { Boolean allowGoogle = applicationProperties.getSystem().getGooglevisibility(); - if(Boolean.TRUE.equals(allowGoogle)) { + if (Boolean.TRUE.equals(allowGoogle)) { return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /"; } else { return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /"; } } - } diff --git a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java index dbb254a7..5b079042 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.controller.web; + import java.time.Duration; import java.time.LocalDateTime; import java.util.Comparator; @@ -22,6 +23,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; + import jakarta.annotation.PostConstruct; import stirling.software.SPDF.config.StartupApplicationListener; import stirling.software.SPDF.model.ApplicationProperties; @@ -31,30 +33,28 @@ import stirling.software.SPDF.model.ApplicationProperties; @Tag(name = "Info", description = "Info APIs") public class MetricsController { - - @Autowired - ApplicationProperties applicationProperties; - - + @Autowired ApplicationProperties applicationProperties; + private final MeterRegistry meterRegistry; private boolean metricsEnabled; - + @PostConstruct public void init() { - Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled(); - if(metricsEnabled == null) - metricsEnabled = true; + Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled(); + if (metricsEnabled == null) metricsEnabled = true; this.metricsEnabled = metricsEnabled; } - + public MetricsController(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @GetMapping("/status") - @Operation(summary = "Application status and version", - description = "This endpoint returns the status of the application and its version number.") + @Operation( + summary = "Application status and version", + description = + "This endpoint returns the status of the application and its version number.") public ResponseEntity getStatus() { if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); @@ -65,38 +65,46 @@ public class MetricsController { status.put("version", getClass().getPackage().getImplementationVersion()); return ResponseEntity.ok(status); } - + @GetMapping("/loads") - @Operation(summary = "GET request count", - description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.") - public ResponseEntity getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { - if (!metricsEnabled) { + @Operation( + summary = "GET request count", + description = + "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.") + public ResponseEntity getPageLoads( + @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") + Optional endpoint) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } - try { + try { double count = 0.0; - + for (Meter meter : meterRegistry.getMeters()) { if (meter.getId().getName().equals("http.requests")) { String method = meter.getId().getTag("method"); if (method != null && method.equals("GET")) { - - if (endpoint.isPresent() && !endpoint.get().isBlank()) { - if(!endpoint.get().startsWith("/")) { - endpoint = Optional.of("/" + endpoint.get()); - } - System.out.println("loads " + endpoint.get() + " vs " + meter.getId().getTag("uri")); - if(endpoint.get().equals(meter.getId().getTag("uri"))){ - if (meter instanceof Counter) { - count += ((Counter) meter).count(); - } - } - } else { - if (meter instanceof Counter) { - count += ((Counter) meter).count(); - } - } + + if (endpoint.isPresent() && !endpoint.get().isBlank()) { + if (!endpoint.get().startsWith("/")) { + endpoint = Optional.of("/" + endpoint.get()); + } + System.out.println( + "loads " + + endpoint.get() + + " vs " + + meter.getId().getTag("uri")); + if (endpoint.get().equals(meter.getId().getTag("uri"))) { + if (meter instanceof Counter) { + count += ((Counter) meter).count(); + } + } + } else { + if (meter instanceof Counter) { + count += ((Counter) meter).count(); + } + } } } } @@ -108,10 +116,11 @@ public class MetricsController { } @GetMapping("/loads/all") - @Operation(summary = "GET requests count for all endpoints", + @Operation( + summary = "GET requests count for all endpoints", description = "This endpoint returns the count of GET requests for each endpoint.") public ResponseEntity getAllEndpointLoads() { - if (!metricsEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { @@ -133,10 +142,11 @@ public class MetricsController { } } - List results = counts.entrySet().stream() - .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) - .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) - .collect(Collectors.toList()); + List results = + counts.entrySet().stream() + .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) + .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) + .collect(Collectors.toList()); return ResponseEntity.ok(results); } catch (Exception e) { @@ -147,35 +157,41 @@ public class MetricsController { public class EndpointCount { private String endpoint; private double count; - - public EndpointCount(String endpoint, double count) { - this.endpoint = endpoint; - this.count = count; - } - public String getEndpoint() { - return endpoint; - } - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - public double getCount() { - return count; - } - public void setCount(double count) { - this.count = count; - } + public EndpointCount(String endpoint, double count) { + this.endpoint = endpoint; + this.count = count; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public double getCount() { + return count; + } + + public void setCount(double count) { + this.count = count; + } } - @GetMapping("/requests") - @Operation(summary = "POST request count", - description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.") - public ResponseEntity getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { - if (!metricsEnabled) { + @Operation( + summary = "POST request count", + description = + "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.") + public ResponseEntity getTotalRequests( + @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") + Optional endpoint) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } - try { + try { double count = 0.0; for (Meter meter : meterRegistry.getMeters()) { @@ -199,18 +215,18 @@ public class MetricsController { } } } - return ResponseEntity.ok(count); + return ResponseEntity.ok(count); } catch (Exception e) { - return ResponseEntity.ok(-1); + return ResponseEntity.ok(-1); } } - @GetMapping("/requests/all") - @Operation(summary = "POST requests count for all endpoints", + @Operation( + summary = "POST requests count for all endpoints", description = "This endpoint returns the count of POST requests for each endpoint.") public ResponseEntity getAllPostRequests() { - if (!metricsEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { @@ -232,10 +248,11 @@ public class MetricsController { } } - List results = counts.entrySet().stream() - .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) - .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) - .collect(Collectors.toList()); + List results = + counts.entrySet().stream() + .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) + .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) + .collect(Collectors.toList()); return ResponseEntity.ok(results); } catch (Exception e) { @@ -243,7 +260,6 @@ public class MetricsController { } } - @GetMapping("/uptime") public ResponseEntity getUptime() { if (!metricsEnabled) { diff --git a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index ce4ae649..b0204779 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -23,7 +23,7 @@ public class OtherWebController { model.addAttribute("currentPage", "compress-pdf"); return "misc/compress-pdf"; } - + @GetMapping("/extract-image-scans") @Hidden public ModelAndView extractImageScansForm() { @@ -31,37 +31,34 @@ public class OtherWebController { modelAndView.addObject("currentPage", "extract-image-scans"); return modelAndView; } - + @GetMapping("/show-javascript") @Hidden public String extractJavascriptForm(Model model) { model.addAttribute("currentPage", "show-javascript"); return "misc/show-javascript"; } - - + @GetMapping("/add-page-numbers") @Hidden public String addPageNumbersForm(Model model) { model.addAttribute("currentPage", "add-page-numbers"); return "misc/add-page-numbers"; } - + @GetMapping("/extract-images") @Hidden public String extractImagesForm(Model model) { model.addAttribute("currentPage", "extract-images"); return "misc/extract-images"; } - + @GetMapping("/flatten") @Hidden public String flattenForm(Model model) { model.addAttribute("currentPage", "flatten"); return "misc/flatten"; } - - @GetMapping("/change-metadata") @Hidden @@ -69,22 +66,25 @@ public class OtherWebController { model.addAttribute("currentPage", "change-metadata"); return "misc/change-metadata"; } - + @GetMapping("/compare") @Hidden public String compareForm(Model model) { model.addAttribute("currentPage", "compare"); return "misc/compare"; } - + public List getAvailableTesseractLanguages() { String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata"; File[] files = new File(tessdataDir).listFiles(); if (files == null) { return Collections.emptyList(); } - return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", "")) - .filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList()); + return Arrays.stream(files) + .filter(file -> file.getName().endsWith(".traineddata")) + .map(file -> file.getName().replace(".traineddata", "")) + .filter(lang -> !lang.equalsIgnoreCase("osd")) + .collect(Collectors.toList()); } @GetMapping("/ocr-pdf") @@ -97,29 +97,28 @@ public class OtherWebController { modelAndView.addObject("currentPage", "ocr-pdf"); return modelAndView; } - - + @GetMapping("/add-image") @Hidden public String overlayImage(Model model) { model.addAttribute("currentPage", "add-image"); return "misc/add-image"; } - + @GetMapping("/adjust-contrast") @Hidden public String contrast(Model model) { model.addAttribute("currentPage", "adjust-contrast"); return "misc/adjust-contrast"; } - + @GetMapping("/repair") @Hidden public String repairForm(Model model) { model.addAttribute("currentPage", "repair"); return "misc/repair"; } - + @GetMapping("/remove-blanks") @Hidden public String removeBlanksForm(Model model) { @@ -140,14 +139,11 @@ public class OtherWebController { model.addAttribute("currentPage", "auto-crop"); return "misc/auto-crop"; } - + @GetMapping("/auto-rename") @Hidden public String autoRenameForm(Model model) { model.addAttribute("currentPage", "auto-rename"); return "misc/auto-rename"; } - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index 2cbf245f..ba67e1d7 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -10,20 +10,21 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Controller @Tag(name = "Security", description = "Security APIs") public class SecurityWebController { - - @GetMapping("/auto-redact") + + @GetMapping("/auto-redact") @Hidden public String autoRedactForm(Model model) { model.addAttribute("currentPage", "auto-redact"); return "security/auto-redact"; } - + @GetMapping("/add-password") @Hidden public String addPasswordForm(Model model) { model.addAttribute("currentPage", "add-password"); return "security/add-password"; } + @GetMapping("/change-permissions") @Hidden public String permissionsForm(Model model) { @@ -44,21 +45,21 @@ public class SecurityWebController { model.addAttribute("currentPage", "add-watermark"); return "security/add-watermark"; } - + @GetMapping("/cert-sign") @Hidden public String certSignForm(Model model) { model.addAttribute("currentPage", "cert-sign"); return "security/cert-sign"; } - + @GetMapping("/sanitize-pdf") @Hidden public String sanitizeForm(Model model) { model.addAttribute("currentPage", "sanitize-pdf"); return "security/sanitize-pdf"; } - + @GetMapping("/get-info-on-pdf") @Hidden public String getInfo(Model model) { diff --git a/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java b/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java index 1838b763..0d707e3b 100644 --- a/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java +++ b/src/main/java/stirling/software/SPDF/model/ApiEndpoint.java @@ -9,14 +9,16 @@ public class ApiEndpoint { private String name; private Map parameters; private String description; - + public ApiEndpoint(String name, JsonNode postNode) { this.name = name; this.parameters = new HashMap<>(); - postNode.path("parameters").forEach(paramNode -> { - String paramName = paramNode.path("name").asText(); - parameters.put(paramName, paramNode); - }); + postNode.path("parameters") + .forEach( + paramNode -> { + String paramName = paramNode.path("name").asText(); + parameters.put(paramName, paramNode); + }); this.description = postNode.path("description").asText(); } @@ -32,11 +34,9 @@ public class ApiEndpoint { public String getDescription() { return description; } - - @Override - public String toString() { - return "ApiEndpoint [name=" + name + ", parameters=" + parameters + "]"; - } - - -} \ No newline at end of file + + @Override + public String toString() { + return "ApiEndpoint [name=" + name + ", parameters=" + parameters + "]"; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java b/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java index 54b61c1b..f19fa1e9 100644 --- a/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java +++ b/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.model; + import java.util.Collection; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -16,9 +17,10 @@ public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { setAuthenticated(false); } - public ApiKeyAuthenticationToken(Object principal, String apiKey, Collection authorities) { + public ApiKeyAuthenticationToken( + Object principal, String apiKey, Collection authorities) { super(authorities); - this.principal = principal; // principal can be a UserDetails object + this.principal = principal; // principal can be a UserDetails object this.credentials = apiKey; super.setAuthenticated(true); // this authentication is trusted } @@ -36,7 +38,8 @@ public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { - throw new IllegalArgumentException("Cannot set this token to trusted. Use constructor which takes a GrantedAuthority list instead."); + throw new IllegalArgumentException( + "Cannot set this token to trusted. Use constructor which takes a GrantedAuthority list instead."); } super.setAuthenticated(false); } diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 36073c88..a41d641c 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -12,357 +12,376 @@ import stirling.software.SPDF.config.YamlPropertySourceFactory; @ConfigurationProperties(prefix = "") @PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class) public class ApplicationProperties { - private Security security; - private System system; - private Ui ui; - private Endpoints endpoints; - private Metrics metrics; - private AutomaticallyGenerated automaticallyGenerated; - private AutoPipeline autoPipeline; - - public AutoPipeline getAutoPipeline() { - return autoPipeline != null ? autoPipeline : new AutoPipeline(); - } - - public void setAutoPipeline(AutoPipeline autoPipeline) { - this.autoPipeline = autoPipeline; - } - - public Security getSecurity() { - return security != null ? security : new Security(); - } - - public void setSecurity(Security security) { - this.security = security; - } - - public System getSystem() { - return system != null ? system : new System(); - } - - public void setSystem(System system) { - this.system = system; - } - - public Ui getUi() { - return ui != null ? ui : new Ui(); - } - - public void setUi(Ui ui) { - this.ui = ui; - } - - public Endpoints getEndpoints() { - return endpoints != null ? endpoints : new Endpoints(); - } - - public void setEndpoints(Endpoints endpoints) { - this.endpoints = endpoints; - } - - public Metrics getMetrics() { - return metrics != null ? metrics : new Metrics(); - } - - public void setMetrics(Metrics metrics) { - this.metrics = metrics; - } - - public AutomaticallyGenerated getAutomaticallyGenerated() { - return automaticallyGenerated != null ? automaticallyGenerated : new AutomaticallyGenerated(); - } - - public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) { - this.automaticallyGenerated = automaticallyGenerated; - } - - @Override - public String toString() { - return "ApplicationProperties [security=" + security + ", system=" + system + ", ui=" + ui + ", endpoints=" - + endpoints + ", metrics=" + metrics + ", automaticallyGenerated=" + automaticallyGenerated - + ", autoPipeline=" + autoPipeline + "]"; - } - - public static class AutoPipeline { - private String outputFolder; - - public String getOutputFolder() { - return outputFolder; - } - - public void setOutputFolder(String outputFolder) { - this.outputFolder = outputFolder; - } - - @Override - public String toString() { - return "AutoPipeline [outputFolder=" + outputFolder + "]"; - } - - - - } - public static class Security { - private Boolean enableLogin; - private Boolean csrfDisabled; - private InitialLogin initialLogin; - private int loginAttemptCount; - private long loginResetTimeMinutes; - - - public int getLoginAttemptCount() { - return loginAttemptCount; - } - - public void setLoginAttemptCount(int loginAttemptCount) { - this.loginAttemptCount = loginAttemptCount; - } - - public long getLoginResetTimeMinutes() { - return loginResetTimeMinutes; - } - - public void setLoginResetTimeMinutes(long loginResetTimeMinutes) { - this.loginResetTimeMinutes = loginResetTimeMinutes; - } - - public InitialLogin getInitialLogin() { - return initialLogin != null ? initialLogin : new InitialLogin(); - } - - public void setInitialLogin(InitialLogin initialLogin) { - this.initialLogin = initialLogin; - } - - public Boolean getEnableLogin() { - return enableLogin; - } - - public void setEnableLogin(Boolean enableLogin) { - this.enableLogin = enableLogin; - } - - public Boolean getCsrfDisabled() { - return csrfDisabled; - } - - public void setCsrfDisabled(Boolean csrfDisabled) { - this.csrfDisabled = csrfDisabled; - } - - - @Override - public String toString() { - return "Security [enableLogin=" + enableLogin + ", initialLogin=" + initialLogin + ", csrfDisabled=" - + csrfDisabled + "]"; - } - - public static class InitialLogin { - - private String username; - private String password; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Override - public String toString() { - return "InitialLogin [username=" + username + ", password=" + (password != null && !password.isEmpty() ? "MASKED" : "NULL") + "]"; - } - - - - } - } - - public static class System { - private String defaultLocale; - private Boolean googlevisibility; - private String rootURIPath; - private String customStaticFilePath; - private Integer maxFileSize; - - private Boolean enableAlphaFunctionality; - - - - - public Boolean getEnableAlphaFunctionality() { - return enableAlphaFunctionality; - } - - public void setEnableAlphaFunctionality(Boolean enableAlphaFunctionality) { - this.enableAlphaFunctionality = enableAlphaFunctionality; - } - - public String getDefaultLocale() { - return defaultLocale; - } - - public void setDefaultLocale(String defaultLocale) { - this.defaultLocale = defaultLocale; - } - - public Boolean getGooglevisibility() { - return googlevisibility; - } - - public void setGooglevisibility(Boolean googlevisibility) { - this.googlevisibility = googlevisibility; - } - - public String getRootURIPath() { - return rootURIPath; - } - - public void setRootURIPath(String rootURIPath) { - this.rootURIPath = rootURIPath; - } - - public String getCustomStaticFilePath() { - return customStaticFilePath; - } - - public void setCustomStaticFilePath(String customStaticFilePath) { - this.customStaticFilePath = customStaticFilePath; - } - - public Integer getMaxFileSize() { - return maxFileSize; - } - - public void setMaxFileSize(Integer maxFileSize) { - this.maxFileSize = maxFileSize; - } - - @Override - public String toString() { - return "System [defaultLocale=" + defaultLocale + ", googlevisibility=" + googlevisibility - + ", rootURIPath=" + rootURIPath + ", customStaticFilePath=" + customStaticFilePath - + ", maxFileSize=" + maxFileSize + ", enableAlphaFunctionality=" + enableAlphaFunctionality + "]"; - } - - - - } - - public static class Ui { - private String appName; - private String homeDescription; - private String appNameNavbar; - - public String getAppName() { - if(appName != null && appName.trim().length() == 0) - return null; - return appName; - } - - public void setAppName(String appName) { - this.appName = appName; - } - - public String getHomeDescription() { - if(homeDescription != null && homeDescription.trim().length() == 0) - return null; - return homeDescription; - } - - public void setHomeDescription(String homeDescription) { - this.homeDescription = homeDescription; - } - - public String getAppNameNavbar() { - if(appNameNavbar != null && appNameNavbar.trim().length() == 0) - return null; - return appNameNavbar; - } - - public void setAppNameNavbar(String appNameNavbar) { - this.appNameNavbar = appNameNavbar; - } - - @Override - public String toString() { - return "UserInterface [appName=" + appName + ", homeDescription=" + homeDescription + ", appNameNavbar=" + appNameNavbar + "]"; - } - } - - - public static class Endpoints { - private List toRemove; - private List groupsToRemove; - - public List getToRemove() { - return toRemove; - } - - public void setToRemove(List toRemove) { - this.toRemove = toRemove; - } - - public List getGroupsToRemove() { - return groupsToRemove; - } - - public void setGroupsToRemove(List groupsToRemove) { - this.groupsToRemove = groupsToRemove; - } - - @Override - public String toString() { - return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]"; - } - - - } - - public static class Metrics { - private Boolean enabled; - - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - @Override - public String toString() { - return "Metrics [enabled=" + enabled + "]"; - } - - - } - - public static class AutomaticallyGenerated { - private String key; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - @Override - public String toString() { - return "AutomaticallyGenerated [key=" + (key != null && !key.isEmpty() ? "MASKED" : "NULL") + "]"; - } - - } + private Security security; + private System system; + private Ui ui; + private Endpoints endpoints; + private Metrics metrics; + private AutomaticallyGenerated automaticallyGenerated; + private AutoPipeline autoPipeline; + + public AutoPipeline getAutoPipeline() { + return autoPipeline != null ? autoPipeline : new AutoPipeline(); + } + + public void setAutoPipeline(AutoPipeline autoPipeline) { + this.autoPipeline = autoPipeline; + } + + public Security getSecurity() { + return security != null ? security : new Security(); + } + + public void setSecurity(Security security) { + this.security = security; + } + + public System getSystem() { + return system != null ? system : new System(); + } + + public void setSystem(System system) { + this.system = system; + } + + public Ui getUi() { + return ui != null ? ui : new Ui(); + } + + public void setUi(Ui ui) { + this.ui = ui; + } + + public Endpoints getEndpoints() { + return endpoints != null ? endpoints : new Endpoints(); + } + + public void setEndpoints(Endpoints endpoints) { + this.endpoints = endpoints; + } + + public Metrics getMetrics() { + return metrics != null ? metrics : new Metrics(); + } + + public void setMetrics(Metrics metrics) { + this.metrics = metrics; + } + + public AutomaticallyGenerated getAutomaticallyGenerated() { + return automaticallyGenerated != null + ? automaticallyGenerated + : new AutomaticallyGenerated(); + } + + public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) { + this.automaticallyGenerated = automaticallyGenerated; + } + + @Override + public String toString() { + return "ApplicationProperties [security=" + + security + + ", system=" + + system + + ", ui=" + + ui + + ", endpoints=" + + endpoints + + ", metrics=" + + metrics + + ", automaticallyGenerated=" + + automaticallyGenerated + + ", autoPipeline=" + + autoPipeline + + "]"; + } + + public static class AutoPipeline { + private String outputFolder; + + public String getOutputFolder() { + return outputFolder; + } + + public void setOutputFolder(String outputFolder) { + this.outputFolder = outputFolder; + } + + @Override + public String toString() { + return "AutoPipeline [outputFolder=" + outputFolder + "]"; + } + } + + public static class Security { + private Boolean enableLogin; + private Boolean csrfDisabled; + private InitialLogin initialLogin; + private int loginAttemptCount; + private long loginResetTimeMinutes; + + public int getLoginAttemptCount() { + return loginAttemptCount; + } + + public void setLoginAttemptCount(int loginAttemptCount) { + this.loginAttemptCount = loginAttemptCount; + } + + public long getLoginResetTimeMinutes() { + return loginResetTimeMinutes; + } + + public void setLoginResetTimeMinutes(long loginResetTimeMinutes) { + this.loginResetTimeMinutes = loginResetTimeMinutes; + } + + public InitialLogin getInitialLogin() { + return initialLogin != null ? initialLogin : new InitialLogin(); + } + + public void setInitialLogin(InitialLogin initialLogin) { + this.initialLogin = initialLogin; + } + + public Boolean getEnableLogin() { + return enableLogin; + } + + public void setEnableLogin(Boolean enableLogin) { + this.enableLogin = enableLogin; + } + + public Boolean getCsrfDisabled() { + return csrfDisabled; + } + + public void setCsrfDisabled(Boolean csrfDisabled) { + this.csrfDisabled = csrfDisabled; + } + + @Override + public String toString() { + return "Security [enableLogin=" + + enableLogin + + ", initialLogin=" + + initialLogin + + ", csrfDisabled=" + + csrfDisabled + + "]"; + } + + public static class InitialLogin { + + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "InitialLogin [username=" + + username + + ", password=" + + (password != null && !password.isEmpty() ? "MASKED" : "NULL") + + "]"; + } + } + } + + public static class System { + private String defaultLocale; + private Boolean googlevisibility; + private String rootURIPath; + private String customStaticFilePath; + private Integer maxFileSize; + + private Boolean enableAlphaFunctionality; + + public Boolean getEnableAlphaFunctionality() { + return enableAlphaFunctionality; + } + + public void setEnableAlphaFunctionality(Boolean enableAlphaFunctionality) { + this.enableAlphaFunctionality = enableAlphaFunctionality; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + public Boolean getGooglevisibility() { + return googlevisibility; + } + + public void setGooglevisibility(Boolean googlevisibility) { + this.googlevisibility = googlevisibility; + } + + public String getRootURIPath() { + return rootURIPath; + } + + public void setRootURIPath(String rootURIPath) { + this.rootURIPath = rootURIPath; + } + + public String getCustomStaticFilePath() { + return customStaticFilePath; + } + + public void setCustomStaticFilePath(String customStaticFilePath) { + this.customStaticFilePath = customStaticFilePath; + } + + public Integer getMaxFileSize() { + return maxFileSize; + } + + public void setMaxFileSize(Integer maxFileSize) { + this.maxFileSize = maxFileSize; + } + + @Override + public String toString() { + return "System [defaultLocale=" + + defaultLocale + + ", googlevisibility=" + + googlevisibility + + ", rootURIPath=" + + rootURIPath + + ", customStaticFilePath=" + + customStaticFilePath + + ", maxFileSize=" + + maxFileSize + + ", enableAlphaFunctionality=" + + enableAlphaFunctionality + + "]"; + } + } + + public static class Ui { + private String appName; + private String homeDescription; + private String appNameNavbar; + + public String getAppName() { + if (appName != null && appName.trim().length() == 0) return null; + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getHomeDescription() { + if (homeDescription != null && homeDescription.trim().length() == 0) return null; + return homeDescription; + } + + public void setHomeDescription(String homeDescription) { + this.homeDescription = homeDescription; + } + + public String getAppNameNavbar() { + if (appNameNavbar != null && appNameNavbar.trim().length() == 0) return null; + return appNameNavbar; + } + + public void setAppNameNavbar(String appNameNavbar) { + this.appNameNavbar = appNameNavbar; + } + + @Override + public String toString() { + return "UserInterface [appName=" + + appName + + ", homeDescription=" + + homeDescription + + ", appNameNavbar=" + + appNameNavbar + + "]"; + } + } + + public static class Endpoints { + private List toRemove; + private List groupsToRemove; + + public List getToRemove() { + return toRemove; + } + + public void setToRemove(List toRemove) { + this.toRemove = toRemove; + } + + public List getGroupsToRemove() { + return groupsToRemove; + } + + public void setGroupsToRemove(List groupsToRemove) { + this.groupsToRemove = groupsToRemove; + } + + @Override + public String toString() { + return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]"; + } + } + + public static class Metrics { + private Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return "Metrics [enabled=" + enabled + "]"; + } + } + + public static class AutomaticallyGenerated { + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public String toString() { + return "AutomaticallyGenerated [key=" + + (key != null && !key.isEmpty() ? "MASKED" : "NULL") + + "]"; + } + } } diff --git a/src/main/java/stirling/software/SPDF/model/AttemptCounter.java b/src/main/java/stirling/software/SPDF/model/AttemptCounter.java index fd668b05..7cb13ee0 100644 --- a/src/main/java/stirling/software/SPDF/model/AttemptCounter.java +++ b/src/main/java/stirling/software/SPDF/model/AttemptCounter.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.model; + public class AttemptCounter { private int attemptCount; private long lastAttemptTime; diff --git a/src/main/java/stirling/software/SPDF/model/Authority.java b/src/main/java/stirling/software/SPDF/model/Authority.java index 8be853ea..57ba538e 100644 --- a/src/main/java/stirling/software/SPDF/model/Authority.java +++ b/src/main/java/stirling/software/SPDF/model/Authority.java @@ -13,19 +13,15 @@ import jakarta.persistence.Table; @Table(name = "authorities") public class Authority { - public Authority() { + public Authority() {} - } - - - public Authority(String authority, User user) { - this.authority = authority; - this.user = user; - user.getAuthorities().add(this); - } + public Authority(String authority, User user) { + this.authority = authority; + this.user = user; + user.getAuthorities().add(this); + } - - @Id + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -36,29 +32,27 @@ public class Authority { @JoinColumn(name = "user_id") private User user; - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public void setId(Long id) { - this.id = id; - } + public void setId(Long id) { + this.id = id; + } - public String getAuthority() { - return authority; - } + public String getAuthority() { + return authority; + } - public void setAuthority(String authority) { - this.authority = authority; - } + public void setAuthority(String authority) { + this.authority = authority; + } - public User getUser() { - return user; - } + public User getUser() { + return user; + } - public void setUser(User user) { - this.user = user; - } - - + public void setUser(User user) { + this.user = user; + } } diff --git a/src/main/java/stirling/software/SPDF/model/PDFText.java b/src/main/java/stirling/software/SPDF/model/PDFText.java index 9a4909d0..9c460f3c 100644 --- a/src/main/java/stirling/software/SPDF/model/PDFText.java +++ b/src/main/java/stirling/software/SPDF/model/PDFText.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.model; + public class PDFText { private final int pageIndex; private final float x1; @@ -39,4 +40,4 @@ public class PDFText { public String getText() { return text; } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/model/PersistentLogin.java b/src/main/java/stirling/software/SPDF/model/PersistentLogin.java index 0747c1eb..cc94eea2 100644 --- a/src/main/java/stirling/software/SPDF/model/PersistentLogin.java +++ b/src/main/java/stirling/software/SPDF/model/PersistentLogin.java @@ -24,38 +24,37 @@ public class PersistentLogin { @Column(name = "last_used", nullable = false) private Date lastUsed; - public String getSeries() { - return series; - } + public String getSeries() { + return series; + } - public void setSeries(String series) { - this.series = series; - } + public void setSeries(String series) { + this.series = series; + } - public String getUsername() { - return username; - } + public String getUsername() { + return username; + } - public void setUsername(String username) { - this.username = username; - } + public void setUsername(String username) { + this.username = username; + } - public String getToken() { - return token; - } + public String getToken() { + return token; + } - public void setToken(String token) { - this.token = token; - } + public void setToken(String token) { + this.token = token; + } - public Date getLastUsed() { - return lastUsed; - } + public Date getLastUsed() { + return lastUsed; + } - public void setLastUsed(Date lastUsed) { - this.lastUsed = lastUsed; - } + public void setLastUsed(Date lastUsed) { + this.lastUsed = lastUsed; + } - // Getters, setters, etc. } diff --git a/src/main/java/stirling/software/SPDF/model/PipelineConfig.java b/src/main/java/stirling/software/SPDF/model/PipelineConfig.java index 77ef7a05..efb9b232 100644 --- a/src/main/java/stirling/software/SPDF/model/PipelineConfig.java +++ b/src/main/java/stirling/software/SPDF/model/PipelineConfig.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.model; + import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; @@ -14,7 +15,6 @@ public class PipelineConfig { @JsonProperty("outputFileName") private String outputPattern; - public String getName() { return name; } @@ -46,6 +46,4 @@ public class PipelineConfig { public void setOutputPattern(String outputPattern) { this.outputPattern = outputPattern; } - - } diff --git a/src/main/java/stirling/software/SPDF/model/PipelineOperation.java b/src/main/java/stirling/software/SPDF/model/PipelineOperation.java index 10c27bfc..f6183505 100644 --- a/src/main/java/stirling/software/SPDF/model/PipelineOperation.java +++ b/src/main/java/stirling/software/SPDF/model/PipelineOperation.java @@ -3,30 +3,27 @@ package stirling.software.SPDF.model; import java.util.Map; public class PipelineOperation { - private String operation; - private Map parameters; + private String operation; + private Map parameters; + public String getOperation() { + return operation; + } - public String getOperation() { - return operation; - } + public void setOperation(String operation) { + this.operation = operation; + } - public void setOperation(String operation) { - this.operation = operation; - } + public Map getParameters() { + return parameters; + } - public Map getParameters() { - return parameters; - } + public void setParameters(Map parameters) { + this.parameters = parameters; + } - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - @Override - public String toString() { - return "PipelineOperation [operation=" + operation + ", parameters=" + parameters + "]"; - } - - - } \ No newline at end of file + @Override + public String toString() { + return "PipelineOperation [operation=" + operation + ", parameters=" + parameters + "]"; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/Role.java b/src/main/java/stirling/software/SPDF/model/Role.java index 85315a16..5100e9dd 100644 --- a/src/main/java/stirling/software/SPDF/model/Role.java +++ b/src/main/java/stirling/software/SPDF/model/Role.java @@ -1,7 +1,8 @@ package stirling.software.SPDF.model; + public enum Role { - - // Unlimited access + + // Unlimited access ADMIN("ROLE_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE), // Unlimited access @@ -15,12 +16,11 @@ public enum Role { // 0 API calls per day and 20 web calls WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20), - - - INTERNAL_API_USER("STIRLING-PDF-BACKEND-API-USER", Integer.MAX_VALUE, Integer.MAX_VALUE), - DEMO_USER("ROLE_DEMO_USER", 100, 100); - + INTERNAL_API_USER("STIRLING-PDF-BACKEND-API-USER", Integer.MAX_VALUE, Integer.MAX_VALUE), + + DEMO_USER("ROLE_DEMO_USER", 100, 100); + private final String roleId; private final int apiCallsPerDay; private final int webCallsPerDay; @@ -42,7 +42,7 @@ public enum Role { public int getWebCallsPerDay() { return webCallsPerDay; } - + public static Role fromString(String roleId) { for (Role role : Role.values()) { if (role.getRoleId().equalsIgnoreCase(roleId)) { @@ -51,5 +51,4 @@ public enum Role { } throw new IllegalArgumentException("No Role defined for id: " + roleId); } - } diff --git a/src/main/java/stirling/software/SPDF/model/SortTypes.java b/src/main/java/stirling/software/SPDF/model/SortTypes.java index 21181cfa..eb10ff58 100644 --- a/src/main/java/stirling/software/SPDF/model/SortTypes.java +++ b/src/main/java/stirling/software/SPDF/model/SortTypes.java @@ -1,4 +1,12 @@ package stirling.software.SPDF.model; + public enum SortTypes { - REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, SIDE_STITCH_BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, -} \ No newline at end of file + REVERSE_ORDER, + DUPLEX_SORT, + BOOKLET_SORT, + SIDE_STITCH_BOOKLET_SORT, + ODD_EVEN_SPLIT, + REMOVE_FIRST, + REMOVE_LAST, + REMOVE_FIRST_AND_LAST, +} diff --git a/src/main/java/stirling/software/SPDF/model/User.java b/src/main/java/stirling/software/SPDF/model/User.java index f771a821..253b33da 100644 --- a/src/main/java/stirling/software/SPDF/model/User.java +++ b/src/main/java/stirling/software/SPDF/model/User.java @@ -19,15 +19,16 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.MapKeyColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; + @Entity @Table(name = "users") public class User { - @Id + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id") private Long id; - + @Column(name = "username", unique = true) private String username; @@ -36,13 +37,13 @@ public class User { @Column(name = "apiKey") private String apiKey; - + @Column(name = "enabled") private boolean enabled; @Column(name = "isFirstLogin") private Boolean isFirstLogin = false; - + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user") private Set authorities = new HashSet<>(); @@ -50,85 +51,83 @@ public class User { @MapKeyColumn(name = "setting_key") @Column(name = "setting_value") @CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id")) - private Map settings = new HashMap<>(); // Key-value pairs of settings. + private Map settings = new HashMap<>(); // Key-value pairs of settings. - - public boolean isFirstLogin() { - return isFirstLogin != null && isFirstLogin; - } + public boolean isFirstLogin() { + return isFirstLogin != null && isFirstLogin; + } - public void setFirstLogin(boolean isFirstLogin) { - this.isFirstLogin = isFirstLogin; - } + public void setFirstLogin(boolean isFirstLogin) { + this.isFirstLogin = isFirstLogin; + } - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public void setId(Long id) { - this.id = id; - } + public void setId(Long id) { + this.id = id; + } - public String getApiKey() { - return apiKey; - } + public String getApiKey() { + return apiKey; + } - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } - public Map getSettings() { - return settings; - } + public Map getSettings() { + return settings; + } - public void setSettings(Map settings) { - this.settings = settings; - } + public void setSettings(Map settings) { + this.settings = settings; + } - public String getUsername() { - return username; - } + public String getUsername() { + return username; + } - public void setUsername(String username) { - this.username = username; - } + public void setUsername(String username) { + this.username = username; + } - public String getPassword() { - return password; - } + public String getPassword() { + return password; + } - public void setPassword(String password) { - this.password = password; - } + public void setPassword(String password) { + this.password = password; + } - public boolean isEnabled() { - return enabled; - } + public boolean isEnabled() { + return enabled; + } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } - public Set getAuthorities() { - return authorities; - } + public Set getAuthorities() { + return authorities; + } - public void setAuthorities(Set authorities) { - this.authorities = authorities; - } - - public void addAuthorities(Set authorities) { - this.authorities.addAll(authorities); - } - public void addAuthority(Authority authorities) { - this.authorities.add(authorities); - } - - public String getRolesAsString() { - return this.authorities.stream() - .map(Authority::getAuthority) - .collect(Collectors.joining(", ")); - } + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + public void addAuthorities(Set authorities) { + this.authorities.addAll(authorities); + } + public void addAuthority(Authority authorities) { + this.authorities.add(authorities); + } + + public String getRolesAsString() { + return this.authorities.stream() + .map(Authority::getAuthority) + .collect(Collectors.joining(", ")); + } } diff --git a/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java b/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java index 441d904a..1c0581cd 100644 --- a/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java +++ b/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -12,6 +13,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class GeneralFile { - @Schema(description = "The input file") - private MultipartFile fileInput; + @Schema(description = "The input file") + private MultipartFile fileInput; } diff --git a/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java b/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java index d830ffeb..d4a4a6bd 100644 --- a/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; diff --git a/src/main/java/stirling/software/SPDF/model/api/ImageFile.java b/src/main/java/stirling/software/SPDF/model/api/ImageFile.java index 02079843..fdc0e6dc 100644 --- a/src/main/java/stirling/software/SPDF/model/api/ImageFile.java +++ b/src/main/java/stirling/software/SPDF/model/api/ImageFile.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -11,6 +12,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @EqualsAndHashCode public class ImageFile { - @Schema(description = "The input image file") + @Schema(description = "The input image file") private MultipartFile fileInput; } diff --git a/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java b/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java index 937a4265..00a34b74 100644 --- a/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java +++ b/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java @@ -3,13 +3,15 @@ package stirling.software.SPDF.model.api; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; + @Data @NoArgsConstructor @EqualsAndHashCode public class MultiplePDFFiles { - @Schema(description = "The input PDF files", type = "array", format = "binary") + @Schema(description = "The input PDF files", type = "array", format = "binary") private MultipartFile[] fileInput; } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java b/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java index 1f902d88..47377188 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java @@ -1,16 +1,18 @@ package stirling.software.SPDF.model.api; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; + @Data @NoArgsConstructor -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFComparison extends PDFFile { - - @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = { - "Greater", "Equal", "Less" }) + + @Schema( + description = "The comparison type, accepts Greater, Equal, Less than", + allowableValues = {"Greater", "Equal", "Less"}) private String comparator; - } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java b/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java index 14462f0a..04042bd8 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java @@ -1,15 +1,15 @@ package stirling.software.SPDF.model.api; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; + @Data @NoArgsConstructor -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFComparisonAndCount extends PDFComparison { - @Schema(description = "Count") + @Schema(description = "Count") private String pageCount; - - } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFFile.java b/src/main/java/stirling/software/SPDF/model/api/PDFFile.java index 378b3c03..68562190 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFFile.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFFile.java @@ -3,11 +3,13 @@ package stirling.software.SPDF.model.api; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; + @Data @EqualsAndHashCode public class PDFFile { - @Schema(description = "The input PDF file") + @Schema(description = "The input PDF file") private MultipartFile fileInput; } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java index aa8fe08b..34f15c13 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java @@ -1,14 +1,16 @@ package stirling.software.SPDF.model.api; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFWithImageFormatRequest extends PDFFile { - @Schema(description = "The output image format e.g., 'png', 'jpeg', or 'gif'", - allowableValues = { "png", "jpeg", "gif" }) + @Schema( + description = "The output image format e.g., 'png', 'jpeg', or 'gif'", + allowableValues = {"png", "jpeg", "gif"}) private String format; } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java index 34542da8..aa664be0 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java @@ -7,36 +7,38 @@ import org.apache.pdfbox.pdmodel.PDDocument; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import stirling.software.SPDF.utils.GeneralUtils; + @Data @NoArgsConstructor -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFWithPageNums extends PDFFile { - - @Schema(description = "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"") + + @Schema( + description = + "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"") private String pageNumbers; - - @Hidden - public List getPageNumbersList(){ - int pageCount = 0; - try { - pageCount = PDDocument.load(getFileInput().getInputStream()).getNumberOfPages(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return GeneralUtils.parsePageString(pageNumbers, pageCount); - - } - - @Hidden - public List getPageNumbersList(PDDocument doc){ - int pageCount = 0; - pageCount = doc.getNumberOfPages(); - return GeneralUtils.parsePageString(pageNumbers, pageCount); - - } + + @Hidden + public List getPageNumbersList() { + int pageCount = 0; + try { + pageCount = PDDocument.load(getFileInput().getInputStream()).getNumberOfPages(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return GeneralUtils.parsePageString(pageNumbers, pageCount); + } + + @Hidden + public List getPageNumbersList(PDDocument doc) { + int pageCount = 0; + pageCount = doc.getNumberOfPages(); + return GeneralUtils.parsePageString(pageNumbers, pageCount); + } } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java index 661a4ffe..139f492d 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java @@ -1,16 +1,17 @@ package stirling.software.SPDF.model.api; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFWithPageSize extends PDFFile { - @Schema(description = "The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL.", - allowableValues = { - "A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL" - }) - private String pageSize; + @Schema( + description = + "The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL.", + allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL"}) + private String pageSize; } diff --git a/src/main/java/stirling/software/SPDF/model/api/SplitPdfBySectionsRequest.java b/src/main/java/stirling/software/SPDF/model/api/SplitPdfBySectionsRequest.java index 14112152..fcc5d5ba 100644 --- a/src/main/java/stirling/software/SPDF/model/api/SplitPdfBySectionsRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/SplitPdfBySectionsRequest.java @@ -1,14 +1,16 @@ package stirling.software.SPDF.model.api; + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @Data @NoArgsConstructor -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class SplitPdfBySectionsRequest extends PDFFile { - @Schema(description = "Number of horizontal divisions for each PDF page", example = "2") + @Schema(description = "Number of horizontal divisions for each PDF page", example = "2") private int horizontalDivisions; @Schema(description = "Number of vertical divisions for each PDF page", example = "2") diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java index 18026618..eaa8e361 100644 --- a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java @@ -1,21 +1,29 @@ package stirling.software.SPDF.model.api.converters; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class ConvertToImageRequest extends PDFFile { - @Schema(description = "The output image format", allowableValues = {"png", "jpeg", "jpg", "gif"}) + @Schema( + description = "The output image format", + allowableValues = {"png", "jpeg", "jpg", "gif"}) private String imageFormat; - @Schema(description = "Choose between a single image containing all pages or separate images for each page", allowableValues = {"single", "multiple"}) + @Schema( + description = + "Choose between a single image containing all pages or separate images for each page", + allowableValues = {"single", "multiple"}) private String singleOrMultiple; - @Schema(description = "The color type of the output image(s)", allowableValues = {"color", "greyscale", "blackwhite"}) + @Schema( + description = "The color type of the output image(s)", + allowableValues = {"color", "greyscale", "blackwhite"}) private String colorType; @Schema(description = "The DPI (dots per inch) for the output image(s)") diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java index 37df6f9e..7630f746 100644 --- a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api.converters; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,15 +14,18 @@ public class ConvertToPdfRequest { @Schema(description = "The input images to be converted to a PDF file") private MultipartFile[] fileInput; - @Schema(description = "Option to determine how the image will fit onto the page", - allowableValues = { "fillPage", "fitDocumentToImage", "maintainAspectRatio" }) + @Schema( + description = "Option to determine how the image will fit onto the page", + allowableValues = {"fillPage", "fitDocumentToImage", "maintainAspectRatio"}) private String fitOption; - - - @Schema(description = "The color type of the output image(s)", allowableValues = {"color", "greyscale", "blackwhite"}) + @Schema( + description = "The color type of the output image(s)", + allowableValues = {"color", "greyscale", "blackwhite"}) private String colorType; - @Schema(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true") + @Schema( + description = "Whether to automatically rotate the images to better fit the PDF page", + example = "true") private boolean autoRotate; } diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java index 0e8b79ad..45064375 100644 --- a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java @@ -1,14 +1,17 @@ package stirling.software.SPDF.model.api.converters; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PdfToPresentationRequest extends PDFFile { - @Schema(description = "The output Presentation format", allowableValues = {"ppt", "pptx", "odp"}) + @Schema( + description = "The output Presentation format", + allowableValues = {"ppt", "pptx", "odp"}) private String outputFormat; } diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java index 687ed621..5759d332 100644 --- a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java @@ -1,14 +1,17 @@ package stirling.software.SPDF.model.api.converters; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PdfToTextOrRTFRequest extends PDFFile { - @Schema(description = "The output Text or RTF format", allowableValues = {"rtf", "txt:Text"}) + @Schema( + description = "The output Text or RTF format", + allowableValues = {"rtf", "txt:Text"}) private String outputFormat; } diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java index 87150c73..db3c3dfb 100644 --- a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java @@ -1,14 +1,17 @@ package stirling.software.SPDF.model.api.converters; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PdfToWordRequest extends PDFFile { - @Schema(description = "The output Word document format", allowableValues = {"doc", "docx", "odt"}) + @Schema( + description = "The output Word document format", + allowableValues = {"doc", "docx", "odt"}) private String outputFormat; } diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java index 4607c153..3e340868 100644 --- a/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.model.api.converters; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java b/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java index bfe87a16..faf955c6 100644 --- a/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java +++ b/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java @@ -1,18 +1,15 @@ package stirling.software.SPDF.model.api.extract; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFFilePage extends PDFFile { - @Schema(description = "Number of chosen page", type = "number") private int pageId; - - } - diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java index 0b6cb1cb..7cd2f76a 100644 --- a/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java @@ -1,12 +1,13 @@ package stirling.software.SPDF.model.api.filter; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFWithPageNums; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class ContainsTextRequest extends PDFWithPageNums { @Schema(description = "The text to check for", required = true) diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java index ce9a9236..00d75139 100644 --- a/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java @@ -1,16 +1,15 @@ package stirling.software.SPDF.model.api.filter; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFComparison; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class FileSizeRequest extends PDFComparison { @Schema(description = "File Size", required = true) private String fileSize; - - } diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java index d5fb9739..949178c5 100644 --- a/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java @@ -1,15 +1,15 @@ package stirling.software.SPDF.model.api.filter; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFComparison; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PageRotationRequest extends PDFComparison { @Schema(description = "Rotation in degrees", required = true) private int rotation; - } diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java index 12083636..c8b46a90 100644 --- a/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java @@ -1,16 +1,15 @@ package stirling.software.SPDF.model.api.filter; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFComparison; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PageSizeRequest extends PDFComparison { @Schema(description = "Standard Page Size", required = true) private String standardPageSize; - - } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java b/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java index 52821515..cec72ce4 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java @@ -1,18 +1,23 @@ package stirling.software.SPDF.model.api.general; + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class CropPdfForm extends PDFFile { - - @Schema(description = "The x-coordinate of the top-left corner of the crop area", type = "number") + @Schema( + description = "The x-coordinate of the top-left corner of the crop area", + type = "number") private float x; - @Schema(description = "The y-coordinate of the top-left corner of the crop area", type = "number") + @Schema( + description = "The y-coordinate of the top-left corner of the crop area", + type = "number") private float y; @Schema(description = "The width of the crop area", type = "number") @@ -21,4 +26,3 @@ public class CropPdfForm extends PDFFile { @Schema(description = "The height of the crop area", type = "number") private float height; } - diff --git a/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java index 4642cb75..1ecdc2ee 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java @@ -1,18 +1,21 @@ package stirling.software.SPDF.model.api.general; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class MergeMultiplePagesRequest extends PDFFile { - @Schema(description = "The number of pages to fit onto a single sheet in the output PDF.", - type = "integer", allowableValues = {"2", "3", "4", "9", "16"}) + @Schema( + description = "The number of pages to fit onto a single sheet in the output PDF.", + type = "integer", + allowableValues = {"2", "3", "4", "9", "16"}) private int pagesPerSheet; - + @Schema(description = "Boolean for if you wish to add border around the pages") private boolean addBorder; } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java index b7b3bda7..0d0a98ae 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java @@ -1,22 +1,24 @@ package stirling.software.SPDF.model.api.general; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.MultiplePDFFiles; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class MergePdfsRequest extends MultiplePDFFiles { - - @Schema(description = "The type of sorting to be applied on the input files before merging.", + + @Schema( + description = "The type of sorting to be applied on the input files before merging.", allowableValues = { - "orderProvided", - "byFileName", - "byDateModified", - "byDateCreated", + "orderProvided", + "byFileName", + "byDateModified", + "byDateCreated", "byPDFTitle" - }, + }, defaultValue = "orderProvided") private String sortType = "orderProvided"; } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java index 13ca7857..458dd699 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/OverlayPdfsRequest.java @@ -1,24 +1,34 @@ package stirling.software.SPDF.model.api.general; + import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data @EqualsAndHashCode(callSuper = true) -public class OverlayPdfsRequest extends PDFFile { +public class OverlayPdfsRequest extends PDFFile { - @Schema(description = "An array of PDF files to be used as overlays on the base PDF. The order in these files is applied based on the selected mode.") + @Schema( + description = + "An array of PDF files to be used as overlays on the base PDF. The order in these files is applied based on the selected mode.") private MultipartFile[] overlayFiles; - @Schema(description = "The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts", required = true) + @Schema( + description = + "The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts", + required = true) private String overlayMode; - @Schema(description = "An array of integers specifying the number of times each corresponding overlay file should be applied in the 'FixedRepeatOverlay' mode. This should match the length of the overlayFiles array.", required = false) + @Schema( + description = + "An array of integers specifying the number of times each corresponding overlay file should be applied in the 'FixedRepeatOverlay' mode. This should match the length of the overlayFiles array.", + required = false) private int[] counts; - + @Schema(description = "Overlay position 0 is Foregound, 1 is Background") private int overlayPosition; } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java index 3e5b4f23..b20ced47 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java @@ -1,23 +1,26 @@ package stirling.software.SPDF.model.api.general; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.SortTypes; import stirling.software.SPDF.model.api.PDFWithPageNums; + @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class RearrangePagesRequest extends PDFWithPageNums { - @Schema(implementation = SortTypes.class, description = "The custom mode for page rearrangement. Valid values are:\n" - + "REVERSE_ORDER: Reverses the order of all pages.\n" - + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " - + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" - + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" - + "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n" - + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n") + @Schema( + implementation = SortTypes.class, + description = + "The custom mode for page rearrangement. Valid values are:\n" + + "REVERSE_ORDER: Reverses the order of all pages.\n" + + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" + + "REMOVE_FIRST: Removes the first page.\n" + + "REMOVE_LAST: Removes the last page.\n" + + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n") private String customMode; - - - } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java index 8f48c605..1efd7049 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java @@ -1,14 +1,18 @@ package stirling.software.SPDF.model.api.general; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class RotatePDFRequest extends PDFFile { - @Schema(description = "The angle by which to rotate the PDF file. This should be a multiple of 90.", example = "90") + @Schema( + description = + "The angle by which to rotate the PDF file. This should be a multiple of 90.", + example = "90") private Integer angle; } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java index ff44b01d..0ba004c9 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java @@ -1,14 +1,17 @@ package stirling.software.SPDF.model.api.general; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFWithPageSize; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class ScalePagesRequest extends PDFWithPageSize { - @Schema(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.") + @Schema( + description = + "The scale of the content on the pages of the output PDF. Acceptable values are floats.") private float scaleFactor; } diff --git a/src/main/java/stirling/software/SPDF/model/api/general/SplitPdfBySizeOrCountRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/SplitPdfBySizeOrCountRequest.java index 087ce80c..8b36c1c8 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/SplitPdfBySizeOrCountRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/SplitPdfBySizeOrCountRequest.java @@ -1,18 +1,26 @@ package stirling.software.SPDF.model.api.general; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class SplitPdfBySizeOrCountRequest extends PDFFile { - @Schema(description = "Determines the type of split: 0 for size, 1 for page count, 2 for document count", required = false, defaultValue = "0") - private int splitType; + @Schema( + description = + "Determines the type of split: 0 for size, 1 for page count, 2 for document count", + required = false, + defaultValue = "0") + private int splitType; - - @Schema(description = "Value for split: size in MB (e.g., '10MB') or number of pages (e.g., '5')", required = false, defaultValue = "10MB") + @Schema( + description = + "Value for split: size in MB (e.g., '10MB') or number of pages (e.g., '5')", + required = false, + defaultValue = "10MB") private String splitValue; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java index 313d42ab..5622903a 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java @@ -1,15 +1,18 @@ package stirling.software.SPDF.model.api.misc; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFWithPageNums; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class AddPageNumbersRequest extends PDFWithPageNums { - @Schema(description = "Custom margin: small/medium/large", allowableValues = {"small", "medium", "large"}) + @Schema( + description = "Custom margin: small/medium/large", + allowableValues = {"small", "medium", "large"}) private String customMargin; @Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9") @@ -21,6 +24,8 @@ public class AddPageNumbersRequest extends PDFWithPageNums { @Schema(description = "Which pages to number, default all") private String pagesToNumber; - @Schema(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"") + @Schema( + description = + "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"") private String customText; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java index c4923746..c60d50e8 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java @@ -1,14 +1,19 @@ package stirling.software.SPDF.model.api.misc; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class AutoSplitPdfRequest extends PDFFile { - @Schema(description = "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", required = false, defaultValue = "false") + @Schema( + description = + "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", + required = false, + defaultValue = "false") private boolean duplexMode; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java index f3028445..33c941af 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java @@ -1,14 +1,19 @@ package stirling.software.SPDF.model.api.misc; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class ExtractHeaderRequest extends PDFFile { - @Schema(description = "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", required = false, defaultValue = "false") + @Schema( + description = + "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", + required = false, + defaultValue = "false") private boolean useFirstTextAsFallback; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java index 1a575fe6..6839451e 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api.misc; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,18 +13,33 @@ public class ExtractImageScansRequest { @Schema(description = "The input file containing image scans", required = true) private MultipartFile fileInput; - @Schema(description = "The angle threshold for the image scan extraction", defaultValue = "5", example = "5") + @Schema( + description = "The angle threshold for the image scan extraction", + defaultValue = "5", + example = "5") private int angleThreshold = 5; - @Schema(description = "The tolerance for the image scan extraction", defaultValue = "20", example = "20") + @Schema( + description = "The tolerance for the image scan extraction", + defaultValue = "20", + example = "20") private int tolerance = 20; - @Schema(description = "The minimum area for the image scan extraction", defaultValue = "8000", example = "8000") + @Schema( + description = "The minimum area for the image scan extraction", + defaultValue = "8000", + example = "8000") private int minArea = 8000; - @Schema(description = "The minimum contour area for the image scan extraction", defaultValue = "500", example = "500") + @Schema( + description = "The minimum contour area for the image scan extraction", + defaultValue = "500", + example = "500") private int minContourArea = 500; - @Schema(description = "The border size for the image scan extraction", defaultValue = "1", example = "1") - private int borderSize =1; + @Schema( + description = "The border size for the image scan extraction", + defaultValue = "1", + example = "1") + private int borderSize = 1; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java index d62890aa..e638d7d2 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java @@ -3,12 +3,13 @@ package stirling.software.SPDF.model.api.misc; import java.util.Map; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class MetadataRequest extends PDFFile { @Schema(description = "Delete all metadata if set to true") @@ -41,6 +42,8 @@ public class MetadataRequest extends PDFFile { @Schema(description = "The trapped status of the document") private String trapped; - @Schema(description = "Map list of key and value of custom parameters. Note these must start with customKey and customValue if they are non-standard") + @Schema( + description = + "Map list of key and value of custom parameters. Note these must start with customKey and customValue if they are non-standard") private Map allRequestParams; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java index bc00cf20..96a787f3 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java @@ -1,16 +1,19 @@ package stirling.software.SPDF.model.api.misc; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class OptimizePdfRequest extends PDFFile { - @Schema(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", - allowableValues = { "1", "2", "3", "4", "5" }) + @Schema( + description = + "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", + allowableValues = {"1", "2", "3", "4", "5"}) private Integer optimizeLevel; @Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.") diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java index 50ec4abb..b057709c 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java @@ -3,23 +3,30 @@ package stirling.software.SPDF.model.api.misc; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class OverlayImageRequest extends PDFFile { @Schema(description = "The image file to be overlaid onto the PDF.") private MultipartFile imageFile; - @Schema(description = "The x-coordinate at which to place the top-left corner of the image.", example = "0") + @Schema( + description = "The x-coordinate at which to place the top-left corner of the image.", + example = "0") private float x; - @Schema(description = "The y-coordinate at which to place the top-left corner of the image.", example = "0") + @Schema( + description = "The y-coordinate at which to place the top-left corner of the image.", + example = "0") private float y; - @Schema(description = "Whether to overlay the image onto every page of the PDF.", example = "false") + @Schema( + description = "Whether to overlay the image onto every page of the PDF.", + example = "false") private boolean everyPage; } diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java index 392f8d54..7d3de3e6 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java @@ -3,12 +3,13 @@ package stirling.software.SPDF.model.api.misc; import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class ProcessPdfWithOcrRequest extends PDFFile { @Schema(description = "List of languages to use in OCR processing") @@ -26,10 +27,15 @@ public class ProcessPdfWithOcrRequest extends PDFFile { @Schema(description = "Clean the final output if set to true") private boolean cleanFinal; - @Schema(description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", allowableValues = {"skip-text", "force-ocr", "Normal"}) + @Schema( + description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", + allowableValues = {"skip-text", "force-ocr", "Normal"}) private String ocrType; - @Schema(description = "Specify the OCR render type, either 'hocr' or 'sandwich'", allowableValues = {"hocr", "sandwich"}, defaultValue = "hocr") + @Schema( + description = "Specify the OCR render type, either 'hocr' or 'sandwich'", + allowableValues = {"hocr", "sandwich"}, + defaultValue = "hocr") private String ocrRenderType = "hocr"; @Schema(description = "Remove images from the output PDF if set to true") diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java index 0d2e11c7..7177449f 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java @@ -1,17 +1,24 @@ package stirling.software.SPDF.model.api.misc; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class RemoveBlankPagesRequest extends PDFFile { - @Schema(description = "The threshold value to determine blank pages", example = "10", defaultValue = "10") + @Schema( + description = "The threshold value to determine blank pages", + example = "10", + defaultValue = "10") private int threshold = 10; - @Schema(description = "The percentage of white color on a page to consider it as blank", example = "99.9", defaultValue = "99.9") + @Schema( + description = "The percentage of white color on a page to consider it as blank", + example = "99.9", + defaultValue = "99.9") private float whitePercent = 99.9f; } diff --git a/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java index ea83e470..99cdfc36 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java @@ -1,30 +1,44 @@ package stirling.software.SPDF.model.api.security; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class AddPasswordRequest extends PDFFile { - @Schema(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)", defaultValue = "") + @Schema( + description = + "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)", + defaultValue = "") private String ownerPassword; - @Schema(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)", defaultValue = "") + @Schema( + description = + "The password to be added to the PDF file (Restricts the opening of the document itself.)", + defaultValue = "") private String password; - @Schema(description = "The length of the encryption key", allowableValues = {"40", "128", "256"}, defaultValue = "256") + @Schema( + description = "The length of the encryption key", + allowableValues = {"40", "128", "256"}, + defaultValue = "256") private int keyLength = 256; @Schema(description = "Whether the document assembly is allowed", example = "false") private boolean canAssembleDocument; - @Schema(description = "Whether content extraction for accessibility is allowed", example = "false") + @Schema( + description = "Whether content extraction for accessibility is allowed", + example = "false") private boolean canExtractContent; - @Schema(description = "Whether content extraction for accessibility is allowed", example = "false") + @Schema( + description = "Whether content extraction for accessibility is allowed", + example = "false") private boolean canExtractForAccessibility; @Schema(description = "Whether form filling is allowed", example = "false") diff --git a/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java index cd800948..861568bf 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java @@ -3,17 +3,19 @@ package stirling.software.SPDF.model.api.security; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class AddWatermarkRequest extends PDFFile { - @Schema(description = "The watermark type (text or image)", - allowableValues = {"text", "image"}, - required = true) + @Schema( + description = "The watermark type (text or image)", + allowableValues = {"text", "image"}, + required = true) private String watermarkType; @Schema(description = "The watermark text") @@ -22,8 +24,9 @@ public class AddWatermarkRequest extends PDFFile { @Schema(description = "The watermark image") private MultipartFile watermarkImage; - @Schema(description = "The selected alphabet", - allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"}, + @Schema( + description = "The selected alphabet", + allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"}, defaultValue = "roman") private String alphabet = "roman"; diff --git a/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java index 94d04d1e..7f31abbb 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java @@ -1,12 +1,13 @@ package stirling.software.SPDF.model.api.security; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class PDFPasswordRequest extends PDFFile { @Schema(description = "The password of the PDF file", required = true) diff --git a/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java index 1966c53a..fce57ff5 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java @@ -1,12 +1,13 @@ package stirling.software.SPDF.model.api.security; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class RedactPdfRequest extends PDFFile { @Schema(description = "List of text to redact from the PDF", type = "string", required = true) diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java index 98c7743f..0e12dfe7 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java @@ -1,12 +1,13 @@ package stirling.software.SPDF.model.api.security; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class SanitizePdfRequest extends PDFFile { @Schema(description = "Remove JavaScript actions from the PDF", defaultValue = "false") diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java index 8e537c6a..a1fc2fce 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java @@ -3,18 +3,23 @@ package stirling.software.SPDF.model.api.security; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; @Data -@EqualsAndHashCode(callSuper=true) +@EqualsAndHashCode(callSuper = true) public class SignPDFWithCertRequest extends PDFFile { - @Schema(description = "The type of the digital certificate", allowableValues = { "PKCS12", "PEM" }) + @Schema( + description = "The type of the digital certificate", + allowableValues = {"PKCS12", "PEM"}) private String certType; - @Schema(description = "The private key for the digital certificate (required for PEM type certificates)") + @Schema( + description = + "The private key for the digital certificate (required for PEM type certificates)") private MultipartFile privateKeyFile; @Schema(description = "The digital certificate (required for PEM type certificates)") @@ -38,6 +43,8 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema(description = "The name of the signer") private String name; - @Schema(description = "The page number where the signature should be visible. This is required if showSignature is set to true") + @Schema( + description = + "The page number where the signature should be visible. This is required if showSignature is set to true") private Integer pageNumber; } diff --git a/src/main/java/stirling/software/SPDF/pdf/ImageFinder.java b/src/main/java/stirling/software/SPDF/pdf/ImageFinder.java index 0f49af76..a710dbd5 100644 --- a/src/main/java/stirling/software/SPDF/pdf/ImageFinder.java +++ b/src/main/java/stirling/software/SPDF/pdf/ImageFinder.java @@ -48,83 +48,84 @@ public class ImageFinder extends org.apache.pdfbox.contentstream.PDFGraphicsStre super.processOperator(operator, operands); } - @Override - public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException { - // TODO Auto-generated method stub - - } + @Override + public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException { + // TODO Auto-generated method stub - @Override - public void drawImage(PDImage pdImage) throws IOException { - // TODO Auto-generated method stub - - } + } - @Override - public void clip(int windingRule) throws IOException { - // TODO Auto-generated method stub - - } + @Override + public void drawImage(PDImage pdImage) throws IOException { + // TODO Auto-generated method stub - @Override - public void moveTo(float x, float y) throws IOException { - // TODO Auto-generated method stub - - } + } - @Override - public void lineTo(float x, float y) throws IOException { - // TODO Auto-generated method stub - - } + @Override + public void clip(int windingRule) throws IOException { + // TODO Auto-generated method stub - @Override - public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { - // TODO Auto-generated method stub - - } + } - @Override - public Point2D getCurrentPoint() throws IOException { - // TODO Auto-generated method stub - return null; - } + @Override + public void moveTo(float x, float y) throws IOException { + // TODO Auto-generated method stub - @Override - public void closePath() throws IOException { - // TODO Auto-generated method stub - - } + } - @Override - public void endPath() throws IOException { - // TODO Auto-generated method stub - - } + @Override + public void lineTo(float x, float y) throws IOException { + // TODO Auto-generated method stub - @Override - public void strokePath() throws IOException { - // TODO Auto-generated method stub - - } + } - @Override - public void fillPath(int windingRule) throws IOException { - // TODO Auto-generated method stub - - } + @Override + public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) + throws IOException { + // TODO Auto-generated method stub - @Override - public void fillAndStrokePath(int windingRule) throws IOException { - // TODO Auto-generated method stub - - } + } - @Override - public void shadingFill(COSName shadingName) throws IOException { - // TODO Auto-generated method stub - - } + @Override + public Point2D getCurrentPoint() throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void closePath() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void endPath() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void strokePath() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void fillPath(int windingRule) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void fillAndStrokePath(int windingRule) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void shadingFill(COSName shadingName) throws IOException { + // TODO Auto-generated method stub + + } // ... rest of the overridden methods } diff --git a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java index f7eb9e3f..cdfb5501 100644 --- a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java +++ b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.pdf; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -13,78 +14,80 @@ import stirling.software.SPDF.model.PDFText; public class TextFinder extends PDFTextStripper { - private final String searchText; - private final boolean useRegex; - private final boolean wholeWordSearch; - private final List textOccurrences = new ArrayList<>(); + private final String searchText; + private final boolean useRegex; + private final boolean wholeWordSearch; + private final List textOccurrences = new ArrayList<>(); - public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch) throws IOException { - this.searchText = searchText.toLowerCase(); - this.useRegex = useRegex; - this.wholeWordSearch = wholeWordSearch; - setSortByPosition(true); - } + public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch) + throws IOException { + this.searchText = searchText.toLowerCase(); + this.useRegex = useRegex; + this.wholeWordSearch = wholeWordSearch; + setSortByPosition(true); + } - private List findOccurrencesInText(String searchText, String content) { - List indexes = new ArrayList<>(); - Pattern pattern; + private List findOccurrencesInText(String searchText, String content) { + List indexes = new ArrayList<>(); + Pattern pattern; - if (useRegex) { - // Use regex-based search - pattern = wholeWordSearch - ? Pattern.compile("(\\b|_|\\.)" + searchText + "(\\b|_|\\.)") - : Pattern.compile(searchText); - } else { - // Use normal text search - pattern = wholeWordSearch - ? Pattern.compile("(\\b|_|\\.)" + Pattern.quote(searchText) + "(\\b|_|\\.)") - : Pattern.compile(Pattern.quote(searchText)); - } + if (useRegex) { + // Use regex-based search + pattern = + wholeWordSearch + ? Pattern.compile("(\\b|_|\\.)" + searchText + "(\\b|_|\\.)") + : Pattern.compile(searchText); + } else { + // Use normal text search + pattern = + wholeWordSearch + ? Pattern.compile( + "(\\b|_|\\.)" + Pattern.quote(searchText) + "(\\b|_|\\.)") + : Pattern.compile(Pattern.quote(searchText)); + } - Matcher matcher = pattern.matcher(content); - while (matcher.find()) { - indexes.add(matcher.start()); - } - return indexes; - } - - @Override - protected void writeString(String text, List textPositions) { - for (Integer index : findOccurrencesInText(searchText, text.toLowerCase())) { - if (index + searchText.length() <= textPositions.size()) { - // Initial values based on the first character - TextPosition first = textPositions.get(index); - float minX = first.getX(); - float minY = first.getY(); - float maxX = first.getX() + first.getWidth(); - float maxY = first.getY() + first.getHeight(); + Matcher matcher = pattern.matcher(content); + while (matcher.find()) { + indexes.add(matcher.start()); + } + return indexes; + } - // Loop over the rest of the characters and adjust bounding box values - for (int i = index; i < index + searchText.length(); i++) { - TextPosition position = textPositions.get(i); - minX = Math.min(minX, position.getX()); - minY = Math.min(minY, position.getY()); - maxX = Math.max(maxX, position.getX() + position.getWidth()); - maxY = Math.max(maxY, position.getY() + position.getHeight()); - } + @Override + protected void writeString(String text, List textPositions) { + for (Integer index : findOccurrencesInText(searchText, text.toLowerCase())) { + if (index + searchText.length() <= textPositions.size()) { + // Initial values based on the first character + TextPosition first = textPositions.get(index); + float minX = first.getX(); + float minY = first.getY(); + float maxX = first.getX() + first.getWidth(); + float maxY = first.getY() + first.getHeight(); - textOccurrences.add(new PDFText( - getCurrentPageNo() - 1, - minX, - minY, - maxX, - maxY, - text - )); - } - } - } + // Loop over the rest of the characters and adjust bounding box values + for (int i = index; i < index + searchText.length(); i++) { + TextPosition position = textPositions.get(i); + minX = Math.min(minX, position.getX()); + minY = Math.min(minY, position.getY()); + maxX = Math.max(maxX, position.getX() + position.getWidth()); + maxY = Math.max(maxY, position.getY() + position.getHeight()); + } - public List getTextLocations(PDDocument document) throws Exception { - this.getText(document); - System.out.println("Found " + textOccurrences.size() + " occurrences of '" + searchText + "' in the document."); + textOccurrences.add( + new PDFText(getCurrentPageNo() - 1, minX, minY, maxX, maxY, text)); + } + } + } - return textOccurrences; - } + public List getTextLocations(PDDocument document) throws Exception { + this.getText(document); + System.out.println( + "Found " + + textOccurrences.size() + + " occurrences of '" + + searchText + + "' in the document."); -} \ No newline at end of file + return textOccurrences; + } +} diff --git a/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java b/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java index 62f546b8..bbf32a07 100644 --- a/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java +++ b/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import stirling.software.SPDF.model.Authority; -public interface AuthorityRepository extends JpaRepository { - //Set findByUsername(String username); +public interface AuthorityRepository extends JpaRepository { + // Set findByUsername(String username); Set findByUser_Username(String username); } diff --git a/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java b/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java index e7753903..a901d3c3 100644 --- a/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java +++ b/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java @@ -10,8 +10,7 @@ import stirling.software.SPDF.model.PersistentLogin; public class JPATokenRepositoryImpl implements PersistentTokenRepository { - @Autowired - private PersistentLoginRepository persistentLoginRepository; + @Autowired private PersistentLoginRepository persistentLoginRepository; @Override public void createNewToken(PersistentRememberMeToken token) { @@ -37,7 +36,8 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository { public PersistentRememberMeToken getTokenForSeries(String seriesId) { PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null); if (token != null) { - return new PersistentRememberMeToken(token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed()); + return new PersistentRememberMeToken( + token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed()); } return null; } diff --git a/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java b/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java index 10c1acae..31841a57 100644 --- a/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java +++ b/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java @@ -4,5 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository; import stirling.software.SPDF.model.PersistentLogin; -public interface PersistentLoginRepository extends JpaRepository { -} +public interface PersistentLoginRepository extends JpaRepository {} diff --git a/src/main/java/stirling/software/SPDF/repository/UserRepository.java b/src/main/java/stirling/software/SPDF/repository/UserRepository.java index 744953d7..d63c4cba 100644 --- a/src/main/java/stirling/software/SPDF/repository/UserRepository.java +++ b/src/main/java/stirling/software/SPDF/repository/UserRepository.java @@ -8,6 +8,6 @@ import stirling.software.SPDF.model.User; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + User findByApiKey(String apiKey); } - diff --git a/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java b/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java index 493ce63e..e84e8d88 100644 --- a/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java @@ -28,5 +28,4 @@ public class ErrorUtils { modelAndView.addObject("stackTrace", stackTrace); return modelAndView; } - } diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index 9515a3ac..5e6825dd 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -14,82 +14,87 @@ import java.util.zip.ZipInputStream; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; public class FileToPdf { - public static byte[] convertHtmlToPdf(byte[] fileBytes, String fileName) throws IOException, InterruptedException { - - Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - Path tempInputFile = null; - byte[] pdfBytes; - try { - if (fileName.endsWith(".html")) { - tempInputFile = Files.createTempFile("input_", ".html"); - Files.write(tempInputFile, fileBytes); - } else { - tempInputFile = unzipAndGetMainHtml(fileBytes); - } - - List command = new ArrayList<>(); - command.add("weasyprint"); - command.add(tempInputFile.toString()); - command.add(tempOutputFile.toString()); - ProcessExecutorResult returnCode; - if (fileName.endsWith(".zip")) { - returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) - .runCommandWithOutputHandling(command, tempInputFile.getParent().toFile()); - } else { - - returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) - .runCommandWithOutputHandling(command); - } - - pdfBytes = Files.readAllBytes(tempOutputFile); - } finally { - // Clean up temporary files - Files.delete(tempOutputFile); - Files.delete(tempInputFile); - - if (fileName.endsWith(".zip")) { - GeneralUtils.deleteDirectory(tempInputFile.getParent()); - } - } - - return pdfBytes; - } - + public static byte[] convertHtmlToPdf(byte[] fileBytes, String fileName) + throws IOException, InterruptedException { - private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException { - Path tempDirectory = Files.createTempDirectory("unzipped_"); - try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(fileBytes))) { - ZipEntry entry = zipIn.getNextEntry(); - while (entry != null) { - Path filePath = tempDirectory.resolve(entry.getName()); - if (entry.isDirectory()) { - Files.createDirectories(filePath); // Explicitly create the directory structure - } else { - Files.createDirectories(filePath.getParent()); // Create parent directories if they don't exist - Files.copy(zipIn, filePath); - } - zipIn.closeEntry(); - entry = zipIn.getNextEntry(); - } - } + Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + Path tempInputFile = null; + byte[] pdfBytes; + try { + if (fileName.endsWith(".html")) { + tempInputFile = Files.createTempFile("input_", ".html"); + Files.write(tempInputFile, fileBytes); + } else { + tempInputFile = unzipAndGetMainHtml(fileBytes); + } - //search for the main HTML file. - try (Stream walk = Files.walk(tempDirectory)) { - List htmlFiles = walk.filter(file -> file.toString().endsWith(".html")) - .collect(Collectors.toList()); + List command = new ArrayList<>(); + command.add("weasyprint"); + command.add(tempInputFile.toString()); + command.add(tempOutputFile.toString()); + ProcessExecutorResult returnCode; + if (fileName.endsWith(".zip")) { + returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling( + command, tempInputFile.getParent().toFile()); + } else { - if (htmlFiles.isEmpty()) { - throw new IOException("No HTML files found in the unzipped directory."); - } + returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling(command); + } - // Prioritize 'index.html' if it exists, otherwise use the first .html file - for (Path htmlFile : htmlFiles) { - if (htmlFile.getFileName().toString().equals("index.html")) { - return htmlFile; - } - } + pdfBytes = Files.readAllBytes(tempOutputFile); + } finally { + // Clean up temporary files + Files.delete(tempOutputFile); + Files.delete(tempInputFile); - return htmlFiles.get(0); - } - } + if (fileName.endsWith(".zip")) { + GeneralUtils.deleteDirectory(tempInputFile.getParent()); + } + } + + return pdfBytes; + } + + private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException { + Path tempDirectory = Files.createTempDirectory("unzipped_"); + try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(fileBytes))) { + ZipEntry entry = zipIn.getNextEntry(); + while (entry != null) { + Path filePath = tempDirectory.resolve(entry.getName()); + if (entry.isDirectory()) { + Files.createDirectories(filePath); // Explicitly create the directory structure + } else { + Files.createDirectories( + filePath.getParent()); // Create parent directories if they don't exist + Files.copy(zipIn, filePath); + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + } + + // search for the main HTML file. + try (Stream walk = Files.walk(tempDirectory)) { + List htmlFiles = + walk.filter(file -> file.toString().endsWith(".html")) + .collect(Collectors.toList()); + + if (htmlFiles.isEmpty()) { + throw new IOException("No HTML files found in the unzipped directory."); + } + + // Prioritize 'index.html' if it exists, otherwise use the first .html file + for (Path htmlFile : htmlFiles) { + if (htmlFile.getFileName().toString().equals("index.html")) { + return htmlFile; + } + } + + return htmlFiles.get(0); + } + } } diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 6de7df14..a1e177e4 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -16,46 +16,50 @@ import java.util.ArrayList; import java.util.List; import org.springframework.web.multipart.MultipartFile; + public class GeneralUtils { - public static void deleteDirectory(Path path) throws IOException { - Files.walkFileTree(path, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } + public static void deleteDirectory(Path path) throws IOException { + Files.walkFileTree( + path, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); } - public static String convertToFileName(String name) { + public static String convertToFileName(String name) { String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); if (safeName.length() > 50) { safeName = safeName.substring(0, 50); } return safeName; } - - - public static boolean isValidURL(String urlStr) { - try { - new URL(urlStr); - return true; - } catch (MalformedURLException e) { - return false; - } - } - public static File multipartToFile(MultipartFile multipart) throws IOException { + public static boolean isValidURL(String urlStr) { + try { + new URL(urlStr); + return true; + } catch (MalformedURLException e) { + return false; + } + } + + public static File multipartToFile(MultipartFile multipart) throws IOException { Path tempFile = Files.createTempFile("overlay-", ".pdf"); - try (InputStream in = multipart.getInputStream(); - FileOutputStream out = new FileOutputStream(tempFile.toFile())) { + try (InputStream in = multipart.getInputStream(); + FileOutputStream out = new FileOutputStream(tempFile.toFile())) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { @@ -64,110 +68,119 @@ public class GeneralUtils { } return tempFile.toFile(); } - - public static Long convertSizeToBytes(String sizeStr) { - if (sizeStr == null) { - return null; - } - - sizeStr = sizeStr.trim().toUpperCase(); - try { - if (sizeStr.endsWith("KB")) { - return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); - } else if (sizeStr.endsWith("MB")) { - return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024); - } else if (sizeStr.endsWith("GB")) { - return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024); - } else if (sizeStr.endsWith("B")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); - } else { - // Assume MB if no unit is specified - return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); - } - } catch (NumberFormatException e) { - // The numeric part of the input string cannot be parsed, handle this case - } - - return null; - } - public static List parsePageString(String pageOrder, int totalPages) { - return parsePageList(pageOrder.split(","), totalPages); - } - public static List parsePageList(String[] pageOrderArr, int totalPages) { - List newPageOrder = new ArrayList<>(); + public static Long convertSizeToBytes(String sizeStr) { + if (sizeStr == null) { + return null; + } - // loop through the page order array - for (String element : pageOrderArr) { - if (element.equalsIgnoreCase("all")) { - for (int i = 0; i < totalPages; i++) { - newPageOrder.add(i); - } - // As all pages are already added, no need to check further - break; - } - else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { - // Handle page order as a function - int coefficient = 0; - int constant = 0; - boolean coefficientExists = false; - boolean constantExists = false; + sizeStr = sizeStr.trim().toUpperCase(); + try { + if (sizeStr.endsWith("KB")) { + return (long) + (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); + } else if (sizeStr.endsWith("MB")) { + return (long) + (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) + * 1024 + * 1024); + } else if (sizeStr.endsWith("GB")) { + return (long) + (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) + * 1024 + * 1024 + * 1024); + } else if (sizeStr.endsWith("B")) { + return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); + } else { + // Assume MB if no unit is specified + return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); + } + } catch (NumberFormatException e) { + // The numeric part of the input string cannot be parsed, handle this case + } - if (element.contains("n")) { - String[] parts = element.split("n"); - if (!parts[0].equals("") && parts[0] != null) { - coefficient = Integer.parseInt(parts[0]); - coefficientExists = true; - } - if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { - constant = Integer.parseInt(parts[1]); - constantExists = true; - } - } else if (element.contains("+")) { - constant = Integer.parseInt(element.replace("+", "")); - constantExists = true; - } + return null; + } - for (int i = 1; i <= totalPages; i++) { - int pageNum = coefficientExists ? coefficient * i : i; - pageNum += constantExists ? constant : 0; + public static List parsePageString(String pageOrder, int totalPages) { + return parsePageList(pageOrder.split(","), totalPages); + } - if (pageNum <= totalPages && pageNum > 0) { - newPageOrder.add(pageNum - 1); - } - } - } else if (element.contains("-")) { - // split the range into start and end page - String[] range = element.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - // check if the end page is greater than total pages - if (end > totalPages) { - end = totalPages; - } - // loop through the range of pages - for (int j = start; j <= end; j++) { - // print the current index - newPageOrder.add(j - 1); - } - } else { - // if the element is a single page - newPageOrder.add(Integer.parseInt(element) - 1); - } - } + public static List parsePageList(String[] pageOrderArr, int totalPages) { + List newPageOrder = new ArrayList<>(); - return newPageOrder; - } - public static boolean createDir(String path) { - Path folder = Paths.get(path); - if (!Files.exists(folder)) { - try { - Files.createDirectories(folder); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - return true; - } + // loop through the page order array + for (String element : pageOrderArr) { + if (element.equalsIgnoreCase("all")) { + for (int i = 0; i < totalPages; i++) { + newPageOrder.add(i); + } + // As all pages are already added, no need to check further + break; + } else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { + // Handle page order as a function + int coefficient = 0; + int constant = 0; + boolean coefficientExists = false; + boolean constantExists = false; + + if (element.contains("n")) { + String[] parts = element.split("n"); + if (!parts[0].equals("") && parts[0] != null) { + coefficient = Integer.parseInt(parts[0]); + coefficientExists = true; + } + if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { + constant = Integer.parseInt(parts[1]); + constantExists = true; + } + } else if (element.contains("+")) { + constant = Integer.parseInt(element.replace("+", "")); + constantExists = true; + } + + for (int i = 1; i <= totalPages; i++) { + int pageNum = coefficientExists ? coefficient * i : i; + pageNum += constantExists ? constant : 0; + + if (pageNum <= totalPages && pageNum > 0) { + newPageOrder.add(pageNum - 1); + } + } + } else if (element.contains("-")) { + // split the range into start and end page + String[] range = element.split("-"); + int start = Integer.parseInt(range[0]); + int end = Integer.parseInt(range[1]); + // check if the end page is greater than total pages + if (end > totalPages) { + end = totalPages; + } + // loop through the range of pages + for (int j = start; j <= end; j++) { + // print the current index + newPageOrder.add(j - 1); + } + } else { + // if the element is a single page + newPageOrder.add(Integer.parseInt(element) - 1); + } + } + + return newPageOrder; + } + + public static boolean createDir(String path) { + Path folder = Paths.get(path); + if (!Files.exists(folder)) { + try { + Files.createDirectories(folder); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + return true; + } } diff --git a/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java b/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java index e34892d5..ede9c4f4 100644 --- a/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java @@ -4,22 +4,29 @@ import java.awt.image.BufferedImage; public class ImageProcessingUtils { - static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) { - BufferedImage convertedImage; - switch (colorType) { - case "greyscale": - convertedImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY); - convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); - break; - case "blackwhite": - convertedImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_BYTE_BINARY); - convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); - break; - default: // full color - convertedImage = sourceImage; - break; - } - return convertedImage; - } - + static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) { + BufferedImage convertedImage; + switch (colorType) { + case "greyscale": + convertedImage = + new BufferedImage( + sourceImage.getWidth(), + sourceImage.getHeight(), + BufferedImage.TYPE_BYTE_GRAY); + convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); + break; + case "blackwhite": + convertedImage = + new BufferedImage( + sourceImage.getWidth(), + sourceImage.getHeight(), + BufferedImage.TYPE_BYTE_BINARY); + convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); + break; + default: // full color + convertedImage = sourceImage; + break; + } + return convertedImage; + } } diff --git a/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java b/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java index 5718cd8a..397ff5bc 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java @@ -1,5 +1,3 @@ package stirling.software.SPDF.utils; -public class PDFManipulationUtils { - -} +public class PDFManipulationUtils {} diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index af658f79..a7f751af 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -23,7 +23,9 @@ import org.springframework.web.multipart.MultipartFile; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; public class PDFToFile { - public ResponseEntity processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException { + public ResponseEntity processPdfToOfficeFormat( + MultipartFile inputFile, String outputFormat, String libreOfficeFilter) + throws IOException, InterruptedException { if (!"application/pdf".equals(inputFile.getContentType())) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); @@ -34,7 +36,18 @@ public class PDFToFile { String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.')); // Validate output format - List allowedFormats = Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "html", "xml", "txt:Text"); + List allowedFormats = + Arrays.asList( + "doc", + "docx", + "odt", + "ppt", + "pptx", + "odp", + "rtf", + "html", + "xml", + "txt:Text"); if (!allowedFormats.contains(outputFormat)) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } @@ -47,15 +60,26 @@ public class PDFToFile { try { // Save the uploaded file to a temporary location tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + Files.copy( + inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); // Prepare the output directory tempOutputDir = Files.createTempDirectory("output_"); // Run the LibreOffice command - List command = new ArrayList<>( - Arrays.asList("soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString())); - ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); + List command = + new ArrayList<>( + Arrays.asList( + "soffice", + "--infilter=" + libreOfficeFilter, + "--convert-to", + outputFormat, + "--outdir", + tempOutputDir.toString(), + tempInputFile.toString())); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) + .runCommandWithOutputHandling(command); // Get output files List outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles()); @@ -89,11 +113,10 @@ public class PDFToFile { } finally { // Clean up the temporary files - if (tempInputFile != null) - Files.delete(tempInputFile); - if (tempOutputDir != null) - FileUtils.deleteDirectory(tempOutputDir.toFile()); + if (tempInputFile != null) Files.delete(tempInputFile); + if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile()); } - return WebResponseUtils.bytesToWebResponse(fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse( + fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM); } } diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 038b8302..677bafd1 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -5,7 +5,6 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -37,38 +36,35 @@ public class PdfUtils { private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); + public static PDRectangle textToPageSize(String size) { + switch (size.toUpperCase()) { + case "A0": + return PDRectangle.A0; + case "A1": + return PDRectangle.A1; + case "A2": + return PDRectangle.A2; + case "A3": + return PDRectangle.A3; + case "A4": + return PDRectangle.A4; + case "A5": + return PDRectangle.A5; + case "A6": + return PDRectangle.A6; + case "LETTER": + return PDRectangle.LETTER; + case "LEGAL": + return PDRectangle.LEGAL; + default: + throw new IllegalArgumentException("Invalid standard page size: " + size); + } + } - public static PDRectangle textToPageSize(String size) { - switch (size.toUpperCase()) { - case "A0": - return PDRectangle.A0; - case "A1": - return PDRectangle.A1; - case "A2": - return PDRectangle.A2; - case "A3": - return PDRectangle.A3; - case "A4": - return PDRectangle.A4; - case "A5": - return PDRectangle.A5; - case "A6": - return PDRectangle.A6; - case "LETTER": - return PDRectangle.LETTER; - case "LEGAL": - return PDRectangle.LEGAL; - default: - throw new IllegalArgumentException("Invalid standard page size: " + size); - } - } - - - - public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { String[] pageOrderArr = pagesToCheck.split(","); - List pageList = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + List pageList = + GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); for (int pageNumber : pageList) { PDPage page = document.getPage(pageNumber); @@ -80,9 +76,11 @@ public class PdfUtils { return false; } - public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) throws IOException { + public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) + throws IOException { String[] pageOrderArr = pageNumbersToCheck.split(","); - List pageList = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + List pageList = + GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); for (int pageNumber : pageList) { PDPage page = document.getPage(pageNumber); @@ -94,15 +92,11 @@ public class PdfUtils { return false; } - public static boolean hasImagesOnPage(PDPage page) throws IOException { ImageFinder imageFinder = new ImageFinder(page); imageFinder.processPage(page); return imageFinder.hasImages(); } - - - public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { PDFTextStripper textStripper = new PDFTextStripper(); @@ -113,12 +107,12 @@ public class PdfUtils { return pageText.contains(phrase); } - - public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) throws IOException { + public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) + throws IOException { PDFTextStripper textStripper = new PDFTextStripper(); String pdfText = ""; - if(pagesToCheck == null || pagesToCheck.equals("all")) { + if (pagesToCheck == null || pagesToCheck.equals("all")) { pdfText = textStripper.getText(pdfDocument); } else { // remove whitespaces @@ -152,15 +146,12 @@ public class PdfUtils { return pdfText.contains(text); } - - - - - public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) throws IOException { + public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) + throws IOException { int actualPageCount = pdfDocument.getNumberOfPages(); pdfDocument.close(); - switch(comparator.toLowerCase()) { + switch (comparator.toLowerCase()) { case "greater": return actualPageCount > pageCount; case "equal": @@ -168,7 +159,8 @@ public class PdfUtils { case "less": return actualPageCount < pageCount; default: - throw new IllegalArgumentException("Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); + throw new IllegalArgumentException( + "Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); } } @@ -189,12 +181,15 @@ public class PdfUtils { // Checks if the actual page size matches the expected page size return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; } - - - - - - public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception { + + public static byte[] convertFromPdf( + byte[] inputStream, + String imageType, + ImageType colorType, + boolean singleImage, + int DPI, + String filename) + throws IOException, Exception { try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { PDFRenderer pdfRenderer = new PDFRenderer(document); int pageCount = document.getNumberOfPages(); @@ -203,7 +198,8 @@ public class PdfUtils { ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (singleImage) { - if (imageType.toLowerCase().equals("tiff") || imageType.toLowerCase().equals("tif")) { + if (imageType.toLowerCase().equals("tiff") + || imageType.toLowerCase().equals("tif")) { // Write the images to the output stream as a TIFF with multiple frames ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); ImageWriteParam param = writer.getDefaultWriteParam(); @@ -227,13 +223,17 @@ public class PdfUtils { } else { // Combine all images into a single big image BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); - BufferedImage combined = new BufferedImage(image.getWidth(), image.getHeight() * pageCount, BufferedImage.TYPE_INT_RGB); + BufferedImage combined = + new BufferedImage( + image.getWidth(), + image.getHeight() * pageCount, + BufferedImage.TYPE_INT_RGB); Graphics g = combined.getGraphics(); for (int i = 0; i < pageCount; ++i) { if (i != 0) { image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); - } + } g.drawImage(image, 0, i * image.getHeight(), null); } @@ -252,7 +252,12 @@ public class PdfUtils { ImageIO.write(image, imageType, baosImage); // Add the image to the zip file - zos.putNextEntry(new ZipEntry(String.format(filename + "_%d.%s", i + 1, imageType.toLowerCase()))); + zos.putNextEntry( + new ZipEntry( + String.format( + filename + "_%d.%s", + i + 1, + imageType.toLowerCase()))); zos.write(baosImage.toByteArray()); } } @@ -267,28 +272,37 @@ public class PdfUtils { throw e; } } - public static byte[] imageToPdf(MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) throws IOException { + + public static byte[] imageToPdf( + MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) + throws IOException { try (PDDocument doc = new PDDocument()) { for (MultipartFile file : files) { - String contentType = file.getContentType(); + String contentType = file.getContentType(); String originalFilename = file.getOriginalFilename(); - if (originalFilename != null && (originalFilename.toLowerCase().endsWith(".tiff") || originalFilename.toLowerCase().endsWith(".tif")) ) { + if (originalFilename != null + && (originalFilename.toLowerCase().endsWith(".tiff") + || originalFilename.toLowerCase().endsWith(".tif"))) { ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next(); reader.setInput(ImageIO.createImageInputStream(file.getInputStream())); int numPages = reader.getNumImages(true); for (int i = 0; i < numPages; i++) { BufferedImage pageImage = reader.read(i); - BufferedImage convertedImage = ImageProcessingUtils.convertColorType(pageImage, colorType); - PDImageXObject pdImage = LosslessFactory.createFromImage(doc, convertedImage); + BufferedImage convertedImage = + ImageProcessingUtils.convertColorType(pageImage, colorType); + PDImageXObject pdImage = + LosslessFactory.createFromImage(doc, convertedImage); addImageToDocument(doc, pdImage, fitOption, autoRotate); } } else { BufferedImage image = ImageIO.read(file.getInputStream()); - BufferedImage convertedImage = ImageProcessingUtils.convertColorType(image, colorType); + BufferedImage convertedImage = + ImageProcessingUtils.convertColorType(image, colorType); // Use JPEGFactory if it's JPEG since JPEG is lossy - PDImageXObject pdImage = (contentType != null && contentType.equals("image/jpeg")) - ? JPEGFactory.createFromImage(doc, convertedImage) - : LosslessFactory.createFromImage(doc, convertedImage); + PDImageXObject pdImage = + (contentType != null && contentType.equals("image/jpeg")) + ? JPEGFactory.createFromImage(doc, convertedImage) + : LosslessFactory.createFromImage(doc, convertedImage); addImageToDocument(doc, pdImage, fitOption, autoRotate); } } @@ -299,11 +313,13 @@ public class PdfUtils { } } - private static void addImageToDocument(PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) throws IOException { + private static void addImageToDocument( + PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) + throws IOException { boolean imageIsLandscape = image.getWidth() > image.getHeight(); PDRectangle pageSize = PDRectangle.A4; - System.out.println(fitOption); + System.out.println(fitOption); if (autoRotate && imageIsLandscape) { pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); @@ -335,7 +351,12 @@ public class PdfUtils { float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2; float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2; - contentStream.drawImage(image, xPos, yPos, image.getWidth() * scaleFactor, image.getHeight() * scaleFactor); + contentStream.drawImage( + image, + xPos, + yPos, + image.getWidth() * scaleFactor, + image.getHeight() * scaleFactor); } } catch (IOException e) { logger.error("Error adding image to PDF", e); @@ -343,8 +364,9 @@ public class PdfUtils { } } - - public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) throws IOException { + public static byte[] overlayImage( + byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) + throws IOException { PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); @@ -352,7 +374,9 @@ public class PdfUtils { int pages = document.getNumberOfPages(); for (int i = 0; i < pages; i++) { PDPage page = document.getPage(i); - try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) { + try (PDPageContentStream contentStream = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true)) { // Create an image object from the image bytes PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); // Draw the image onto the page at the specified x and y coordinates @@ -366,7 +390,6 @@ public class PdfUtils { logger.error("Error overlaying image onto PDF", e); throw e; } - } // Create a ByteArrayOutputStream to save the PDF to ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -374,8 +397,4 @@ public class PdfUtils { logger.info("PDF successfully saved to byte array"); return baos.toByteArray(); } - - - - } diff --git a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java index fe5c6717..385f3b80 100644 --- a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java +++ b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java @@ -14,22 +14,29 @@ import java.util.concurrent.Semaphore; public class ProcessExecutor { public enum Processes { - LIBRE_OFFICE, OCR_MY_PDF, PYTHON_OPENCV, GHOSTSCRIPT, WEASYPRINT + LIBRE_OFFICE, + OCR_MY_PDF, + PYTHON_OPENCV, + GHOSTSCRIPT, + WEASYPRINT } private static final Map instances = new ConcurrentHashMap<>(); public static ProcessExecutor getInstance(Processes processType) { - return instances.computeIfAbsent(processType, key -> { - int semaphoreLimit = switch (key) { - case LIBRE_OFFICE -> 1; - case OCR_MY_PDF -> 2; - case PYTHON_OPENCV -> 8; - case GHOSTSCRIPT -> 16; - case WEASYPRINT -> 16; - }; - return new ProcessExecutor(semaphoreLimit); - }); + return instances.computeIfAbsent( + processType, + key -> { + int semaphoreLimit = + switch (key) { + case LIBRE_OFFICE -> 1; + case OCR_MY_PDF -> 2; + case PYTHON_OPENCV -> 8; + case GHOSTSCRIPT -> 16; + case WEASYPRINT -> 16; + }; + return new ProcessExecutor(semaphoreLimit); + }); } private final Semaphore semaphore; @@ -37,10 +44,14 @@ public class ProcessExecutor { private ProcessExecutor(int semaphoreLimit) { this.semaphore = new Semaphore(semaphoreLimit); } - public ProcessExecutorResult runCommandWithOutputHandling(List command) throws IOException, InterruptedException { - return runCommandWithOutputHandling(command, null); + + public ProcessExecutorResult runCommandWithOutputHandling(List command) + throws IOException, InterruptedException { + return runCommandWithOutputHandling(command, null); } - public ProcessExecutorResult runCommandWithOutputHandling(List command, File workingDirectory) throws IOException, InterruptedException { + + public ProcessExecutorResult runCommandWithOutputHandling( + List command, File workingDirectory) throws IOException, InterruptedException { int exitCode = 1; String messages = ""; semaphore.acquire(); @@ -48,7 +59,7 @@ public class ProcessExecutor { System.out.print("Running command: " + String.join(" ", command)); ProcessBuilder processBuilder = new ProcessBuilder(command); - + // Use the working directory if it's set if (workingDirectory != null) { processBuilder.directory(workingDirectory); @@ -59,27 +70,39 @@ public class ProcessExecutor { List errorLines = new ArrayList<>(); List outputLines = new ArrayList<>(); - Thread errorReaderThread = new Thread(() -> { - try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = errorReader.readLine()) != null) { - errorLines.add(line); - } - } catch (IOException e) { - e.printStackTrace(); - } - }); + Thread errorReaderThread = + new Thread( + () -> { + try (BufferedReader errorReader = + new BufferedReader( + new InputStreamReader( + process.getErrorStream(), + StandardCharsets.UTF_8))) { + String line; + while ((line = errorReader.readLine()) != null) { + errorLines.add(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); - Thread outputReaderThread = new Thread(() -> { - try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = outputReader.readLine()) != null) { - outputLines.add(line); - } - } catch (IOException e) { - e.printStackTrace(); - } - }); + Thread outputReaderThread = + new Thread( + () -> { + try (BufferedReader outputReader = + new BufferedReader( + new InputStreamReader( + process.getInputStream(), + StandardCharsets.UTF_8))) { + String line; + while ((line = outputReader.readLine()) != null) { + outputLines.add(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); errorReaderThread.start(); outputReaderThread.start(); @@ -90,7 +113,7 @@ public class ProcessExecutor { // Wait for the reader threads to finish errorReaderThread.join(); outputReaderThread.join(); - + if (outputLines.size() > 0) { String outputMessage = String.join("\n", outputLines); messages += outputMessage; @@ -102,7 +125,11 @@ public class ProcessExecutor { messages += errorMessage; System.out.println("Command error output:\n" + errorMessage); if (exitCode != 0) { - throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage); + throw new IOException( + "Command process failed with exit code " + + exitCode + + ". Error message: " + + errorMessage); } } } finally { @@ -110,26 +137,30 @@ public class ProcessExecutor { } return new ProcessExecutorResult(exitCode, messages); } - public class ProcessExecutorResult{ - int rc; - String messages; - public ProcessExecutorResult(int rc, String messages) { - this.rc = rc; - this.messages = messages; - } - public int getRc() { - return rc; - } - public void setRc(int rc) { - this.rc = rc; - } - public String getMessages() { - return messages; - } - public void setMessages(String messages) { - this.messages = messages; - } - - + + public class ProcessExecutorResult { + int rc; + String messages; + + public ProcessExecutorResult(int rc, String messages) { + this.rc = rc; + this.messages = messages; + } + + public int getRc() { + return rc; + } + + public void setRc(int rc) { + this.rc = rc; + } + + public String getMessages() { + return messages; + } + + public void setMessages(String messages) { + this.messages = messages; + } } } diff --git a/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java b/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java index 8d12267c..aa3e453d 100644 --- a/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java +++ b/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java @@ -4,46 +4,35 @@ import java.util.List; public class PropertyConfigs { - - public static boolean getBooleanValue(List keys, boolean defaultValue) { - for (String key : keys) { - String value = System.getProperty(key); - if (value == null) - value = System.getenv(key); - - if (value != null) - return Boolean.valueOf(value); - } - return defaultValue; - } + public static boolean getBooleanValue(List keys, boolean defaultValue) { + for (String key : keys) { + String value = System.getProperty(key); + if (value == null) value = System.getenv(key); - public static String getStringValue(List keys, String defaultValue) { - for (String key : keys) { - String value = System.getProperty(key); - if (value == null) - value = System.getenv(key); - - if (value != null) - return value; - } - return defaultValue; - } + if (value != null) return Boolean.valueOf(value); + } + return defaultValue; + } - - - - public static boolean getBooleanValue(String key, boolean defaultValue) { + public static String getStringValue(List keys, String defaultValue) { + for (String key : keys) { + String value = System.getProperty(key); + if (value == null) value = System.getenv(key); + + if (value != null) return value; + } + return defaultValue; + } + + public static boolean getBooleanValue(String key, boolean defaultValue) { String value = System.getProperty(key); - if (value == null) - value = System.getenv(key); + if (value == null) value = System.getenv(key); return (value != null) ? Boolean.valueOf(value) : defaultValue; } - public static String getStringValue(String key, String defaultValue) { + public static String getStringValue(String key, String defaultValue) { String value = System.getProperty(key); - if (value == null) - value = System.getenv(key); + if (value == null) value = System.getenv(key); return (value != null) ? value : defaultValue; } - } diff --git a/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java b/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java index 0046ee9f..b320f67e 100644 --- a/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java @@ -2,15 +2,13 @@ package stirling.software.SPDF.utils; public class RequestUriUtils { - public static boolean isStaticResource(String requestURI) { - - return requestURI.startsWith("/css/") - || requestURI.startsWith("/js/") - || requestURI.startsWith("/images/") - || requestURI.startsWith("/public/") - || requestURI.startsWith("/pdfjs/") - || requestURI.endsWith(".svg"); - - } + public static boolean isStaticResource(String requestURI) { + return requestURI.startsWith("/css/") + || requestURI.startsWith("/js/") + || requestURI.startsWith("/images/") + || requestURI.startsWith("/public/") + || requestURI.startsWith("/pdfjs/") + || requestURI.endsWith(".svg"); + } } diff --git a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java index 131aaf03..4958a00d 100644 --- a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java @@ -12,53 +12,56 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; - public class WebResponseUtils { - public static ResponseEntity boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException { - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); - } + public static ResponseEntity boasToWebResponse( + ByteArrayOutputStream baos, String docName) throws IOException { + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); + } - public static ResponseEntity boasToWebResponse(ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); - } + public static ResponseEntity boasToWebResponse( + ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); + } + public static ResponseEntity multiPartFileToWebResponse(MultipartFile file) + throws IOException { + String fileName = file.getOriginalFilename(); + MediaType mediaType = MediaType.parseMediaType(file.getContentType()); - public static ResponseEntity multiPartFileToWebResponse(MultipartFile file) throws IOException { - String fileName = file.getOriginalFilename(); - MediaType mediaType = MediaType.parseMediaType(file.getContentType()); + byte[] bytes = file.getBytes(); - byte[] bytes = file.getBytes(); - - return bytesToWebResponse(bytes, fileName, mediaType); - } + return bytesToWebResponse(bytes, fileName, mediaType); + } - public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName, MediaType mediaType) throws IOException { - - // Return the PDF as a response - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(mediaType); - headers.setContentLength(bytes.length); - String encodedDocName = URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"); - headers.setContentDispositionFormData("attachment", encodedDocName); - return new ResponseEntity<>(bytes, headers, HttpStatus.OK); - } + public static ResponseEntity bytesToWebResponse( + byte[] bytes, String docName, MediaType mediaType) throws IOException { - public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) throws IOException { - return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); - } + // Return the PDF as a response + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(mediaType); + headers.setContentLength(bytes.length); + String encodedDocName = + URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20"); + headers.setContentDispositionFormData("attachment", encodedDocName); + return new ResponseEntity<>(bytes, headers, HttpStatus.OK); + } - public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) throws IOException { - - // Open Byte Array and save document to it - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - // Close the document - document.close(); - - return boasToWebResponse(baos, docName); - } - + public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) + throws IOException { + return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); + } + public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) + throws IOException { + // Open Byte Array and save document to it + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + // Close the document + document.close(); + + return boasToWebResponse(baos, docName); + } } diff --git a/src/main/resources/static/pdfjs/cmaps/CNS2-V.bcmap b/src/main/resources/static/pdfjs/cmaps/CNS2-V.bcmap index 7588cec8..9cfbf508 100644 --- a/src/main/resources/static/pdfjs/cmaps/CNS2-V.bcmap +++ b/src/main/resources/static/pdfjs/cmaps/CNS2-V.bcmap @@ -1,3 +1,3 @@ -àRCopyright 1990-2009 Adobe Systems Incorporated. -All rights reserved. +àRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. See ./LICENSEáCNS2-H \ No newline at end of file diff --git a/src/main/resources/static/pdfjs/cmaps/ETenms-B5-H.bcmap b/src/main/resources/static/pdfjs/cmaps/ETenms-B5-H.bcmap index a7d69db5..c76f5f98 100644 --- a/src/main/resources/static/pdfjs/cmaps/ETenms-B5-H.bcmap +++ b/src/main/resources/static/pdfjs/cmaps/ETenms-B5-H.bcmap @@ -1,3 +1,3 @@ -àRCopyright 1990-2009 Adobe Systems Incorporated. -All rights reserved. +àRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. See ./LICENSEá ETen-B5-H` ^ \ No newline at end of file