Compare commits

..

1 Commits

Author SHA1 Message Date
stirlingbot[bot]
21811fec7d
📁 pre-commit
Signed-off-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-05-12 00:16:28 +00:00
172 changed files with 1775 additions and 6336 deletions

View File

@ -180,7 +180,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image - name: Build and push PR-specific image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile

View File

@ -24,4 +24,4 @@ jobs:
- name: "Checkout Repository" - name: "Checkout Repository"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Dependency Review" - name: "Dependency Review"
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0

View File

@ -38,7 +38,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "adopt" distribution: "adopt"
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: check the licenses for compatibility - name: check the licenses for compatibility
run: ./gradlew clean checkLicense run: ./gradlew clean checkLicense

View File

@ -68,7 +68,7 @@ jobs:
java-version: "21" java-version: "21"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
with: with:
gradle-version: 8.14 gradle-version: 8.14
@ -156,7 +156,7 @@ jobs:
java-version: "21" java-version: "21"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
with: with:
gradle-version: 8.14 gradle-version: 8.14

View File

@ -30,7 +30,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
with: with:
gradle-version: 8.14 gradle-version: 8.14
@ -90,7 +90,7 @@ jobs:
- name: Build and push main Dockerfile - name: Build and push main Dockerfile
id: build-push-regular id: build-push-regular
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
context: . context: .
@ -135,7 +135,7 @@ jobs:
- name: Build and push Dockerfile-ultra-lite - name: Build and push Dockerfile-ultra-lite
id: build-push-lite id: build-push-lite
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
context: . context: .
@ -166,7 +166,7 @@ jobs:
- name: Build and push main Dockerfile fat - name: Build and push main Dockerfile fat
id: build-push-fat id: build-push-fat
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}

View File

@ -35,7 +35,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
with: with:
gradle-version: 8.14 gradle-version: 8.14

View File

@ -74,6 +74,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -27,7 +27,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Build and analyze with Gradle - name: Build and analyze with Gradle
env: env:

View File

@ -26,7 +26,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Generate Swagger documentation - name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs run: ./gradlew generateOpenApiDocs

View File

@ -46,7 +46,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push test image - name: Build and push test image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile

View File

@ -1,24 +0,0 @@
# Codex Contribution Guidelines for Stirling-PDF
This file provides high-level instructions for Codex when modifying any files within this repository. Follow these rules to ensure changes remain consistent with the existing project structure.
## 1. Code Style and Formatting
- Respect the `.editorconfig` settings located in the repository root. Java files use 4 spaces; HTML, JS, and Python generally use 2 spaces. Lines should end with `LF`.
- Format Java code with `./gradlew spotlessApply` before committing.
- Review `DeveloperGuide.md` for project structure and design details before making significant changes.
## 2. Testing
- Run `./gradlew build` before committing changes to ensure the project compiles.
- If the build cannot complete due to environment restrictions, DO NOT COMMIT THE CHANGE
## 3. Commits
- Keep commits focused. Group related changes together and provide concise commit messages.
- Ensure the working tree is clean (`git status`) before concluding your work.
## 4. Pull Requests
- Summarize what was changed and why. Include build results from `./gradlew build` in the PR description.
- Note that the code was generated with the assistance of AI.
## 5. Translations
- Only modify `messages_en_GB.properties` when adding or updating translations.

View File

@ -541,7 +541,7 @@ This would generate n entries of tr for each person in exampleData
```html ```html
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" th:href="@{'/new-feature'}">New Feature</a> <a class="nav-link" th:href="@{/new-feature}">New Feature</a>
</li> </li>
``` ```

View File

@ -112,7 +112,7 @@ Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.sti
## Supported Languages ## Supported Languages
Stirling-PDF currently supports 40 languages! Stirling-PDF currently supports 39 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
@ -148,7 +148,7 @@ Stirling-PDF currently supports 40 languages!
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) | | Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![69%](https://geps.dev/progress/69) | | Slovakian (Slovensky) (sk_SK) | ![69%](https://geps.dev/progress/69) |
| Slovenian (Slovenščina) (sl_SI) | ![94%](https://geps.dev/progress/94) | | Slovenian (Slovenščina) (sl_SI) | ![94%](https://geps.dev/progress/94) |
| Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) | | Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![87%](https://geps.dev/progress/87) | | Swedish (Svenska) (sv_SE) | ![87%](https://geps.dev/progress/87) |
| Thai (ไทย) (th_TH) | ![80%](https://geps.dev/progress/80) | | Thai (ไทย) (th_TH) | ![80%](https://geps.dev/progress/80) |
| Tibetan (བོད་ཡིག་) (zh_BO) | ![88%](https://geps.dev/progress/88) | | Tibetan (བོད་ཡིག་) (zh_BO) | ![88%](https://geps.dev/progress/88) |
@ -156,7 +156,7 @@ Stirling-PDF currently supports 40 languages!
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) | | Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) | | Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) | | Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) |
| Malayalam (മലയാളം) (ml_ML) | ![99%](https://geps.dev/progress/99) |
## Stirling PDF Enterprise ## Stirling PDF Enterprise

View File

@ -1,6 +1,5 @@
plugins { plugins {
id "java" id "java"
id 'jacoco'
id "org.springframework.boot" version "3.4.5" id "org.springframework.boot" version "3.4.5"
id "io.spring.dependency-management" version "1.1.7" id "io.spring.dependency-management" version "1.1.7"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0" id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
@ -10,7 +9,7 @@ plugins {
id "com.github.jk1.dependency-license-report" version "2.9" id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.1" id("org.panteleyev.jpackageplugin") version "1.6.1"
id "org.sonarqube" version "6.2.0.5505" id "org.sonarqube" version "6.1.0.5360"
} }
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
@ -24,13 +23,13 @@ ext {
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.38" lombokVersion = "1.18.38"
bouncycastleVersion = "1.80" bouncycastleVersion = "1.80"
springSecuritySamlVersion = "6.5.0" springSecuritySamlVersion = "6.4.5"
openSamlVersion = "4.3.2" openSamlVersion = "4.3.2"
tempJrePath = null tempJrePath = null
} }
group = "stirling.software" group = "stirling.software"
version = "0.46.2" version = "0.46.1"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@ -434,7 +433,7 @@ dependencies {
} }
//security updates //security updates
implementation "org.springframework:spring-webmvc:6.2.7" implementation "org.springframework:spring-webmvc:6.2.6"
implementation("io.github.pixee:java-security-toolkit:1.2.1") implementation("io.github.pixee:java-security-toolkit:1.2.1")
@ -459,7 +458,7 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-mail:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-mail:$springBootVersion"
implementation "org.springframework.session:spring-session-core:3.4.3" implementation "org.springframework.session:spring-session-core:3.4.3"
implementation "org.springframework:spring-jdbc:6.2.7" implementation "org.springframework:spring-jdbc:6.2.6"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database // Don't upgrade h2database
@ -480,7 +479,7 @@ dependencies {
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
// Batik // Batik
implementation "org.apache.xmlgraphics:batik-all:1.19" implementation "org.apache.xmlgraphics:batik-all:1.18"
// TwelveMonkeys // TwelveMonkeys
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion" runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
@ -528,7 +527,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.15.0" implementation "io.micrometer:micrometer-core:1.14.6"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0" implementation "org.commonmark:commonmark:0.24.0"
@ -543,10 +542,6 @@ dependencies {
compileOnly "org.projectlombok:lombok:$lombokVersion" compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion" annotationProcessor "org.projectlombok:lombok:$lombokVersion"
// Mockito (core)
testImplementation 'org.mockito:mockito-core:5.17.0'
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0' testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
// Apply the foojay-resolver plugin to allow automatic download of JDKs // Apply the foojay-resolver plugin to allow automatic download of JDKs
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
} }
rootProject.name = 'Stirling-PDF' rootProject.name = 'Stirling-PDF'

View File

@ -61,7 +61,6 @@ public class EEAppConfig {
} }
// TODO: Remove post migration // TODO: Remove post migration
@SuppressWarnings("deprecation")
public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) { public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) {
EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition(); EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition();
Premium premium = applicationProperties.getPremium(); Premium premium = applicationProperties.getPremium();

View File

@ -48,47 +48,30 @@ public class KeygenLicenseVerifier {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
// Shared HTTP client for connection pooling
private static final HttpClient httpClient =
HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(java.time.Duration.ofSeconds(10))
.build();
// License metadata context class to avoid shared mutable state
private static class LicenseContext {
private boolean isFloatingLicense = false;
private int maxMachines = 1; // Default to 1 if not specified
private boolean isEnterpriseLicense = false;
public LicenseContext() {}
}
public License verifyLicense(String licenseKeyOrCert) { public License verifyLicense(String licenseKeyOrCert) {
License license; License license;
LicenseContext context = new LicenseContext();
if (isCertificateLicense(licenseKeyOrCert)) { if (isCertificateLicense(licenseKeyOrCert)) {
log.info("Detected certificate-based license. Processing..."); log.info("Detected certificate-based license. Processing...");
boolean isValid = verifyCertificateLicense(licenseKeyOrCert, context); boolean isValid = verifyCertificateLicense(licenseKeyOrCert);
if (isValid) { if (isValid) {
license = context.isEnterpriseLicense ? License.ENTERPRISE : License.PRO; license = isEnterpriseLicense ? License.ENTERPRISE : License.PRO;
} else { } else {
license = License.NORMAL; license = License.NORMAL;
} }
} else if (isJWTLicense(licenseKeyOrCert)) { } else if (isJWTLicense(licenseKeyOrCert)) {
log.info("Detected JWT-style license key. Processing..."); log.info("Detected JWT-style license key. Processing...");
boolean isValid = verifyJWTLicense(licenseKeyOrCert, context); boolean isValid = verifyJWTLicense(licenseKeyOrCert);
if (isValid) { if (isValid) {
license = context.isEnterpriseLicense ? License.ENTERPRISE : License.PRO; license = isEnterpriseLicense ? License.ENTERPRISE : License.PRO;
} else { } else {
license = License.NORMAL; license = License.NORMAL;
} }
} else { } else {
log.info("Detected standard license key. Processing..."); log.info("Detected standard license key. Processing...");
boolean isValid = verifyStandardLicense(licenseKeyOrCert, context); boolean isValid = verifyStandardLicense(licenseKeyOrCert);
if (isValid) { if (isValid) {
license = context.isEnterpriseLicense ? License.ENTERPRISE : License.PRO; license = isEnterpriseLicense ? License.ENTERPRISE : License.PRO;
} else { } else {
license = License.NORMAL; license = License.NORMAL;
} }
@ -96,7 +79,7 @@ public class KeygenLicenseVerifier {
return license; return license;
} }
// Removed instance field for isEnterpriseLicense, now using LicenseContext private boolean isEnterpriseLicense = false;
private boolean isCertificateLicense(String license) { private boolean isCertificateLicense(String license) {
return license != null && license.trim().startsWith(CERT_PREFIX); return license != null && license.trim().startsWith(CERT_PREFIX);
@ -106,7 +89,7 @@ public class KeygenLicenseVerifier {
return license != null && license.trim().startsWith(JWT_PREFIX); return license != null && license.trim().startsWith(JWT_PREFIX);
} }
private boolean verifyCertificateLicense(String licenseFile, LicenseContext context) { private boolean verifyCertificateLicense(String licenseFile) {
try { try {
String encodedPayload = licenseFile; String encodedPayload = licenseFile;
// Remove the header // Remove the header
@ -161,7 +144,7 @@ public class KeygenLicenseVerifier {
} }
// Process the certificate data // Process the certificate data
boolean isValid = processCertificateData(decodedData, context); boolean isValid = processCertificateData(decodedData);
return isValid; return isValid;
} catch (Exception e) { } catch (Exception e) {
@ -204,7 +187,7 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean processCertificateData(String certData, LicenseContext context) { private boolean processCertificateData(String certData) {
try { try {
JSONObject licenseData = new JSONObject(certData); JSONObject licenseData = new JSONObject(certData);
JSONObject metaObj = licenseData.optJSONObject("meta"); JSONObject metaObj = licenseData.optJSONObject("meta");
@ -246,17 +229,15 @@ public class KeygenLicenseVerifier {
if (attributesObj != null) { if (attributesObj != null) {
log.info("Found attributes in certificate data"); log.info("Found attributes in certificate data");
// Check for floating license
context.isFloatingLicense = attributesObj.optBoolean("floating", false);
context.maxMachines = attributesObj.optInt("maxMachines", 1);
// Extract metadata // Extract metadata
JSONObject metadataObj = attributesObj.optJSONObject("metadata"); JSONObject metadataObj = attributesObj.optJSONObject("metadata");
if (metadataObj != null) { if (metadataObj != null) {
int users = metadataObj.optInt("users", 1); int users = metadataObj.optInt("users", 0);
if (users > 0) {
applicationProperties.getPremium().setMaxUsers(users); applicationProperties.getPremium().setMaxUsers(users);
log.info("License allows for {} users", users); log.info("License allows for {} users", users);
context.isEnterpriseLicense = metadataObj.optBoolean("isEnterprise", false); }
isEnterpriseLicense = metadataObj.optBoolean("isEnterprise", false);
} }
// Check license status if available // Check license status if available
@ -276,7 +257,7 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean verifyJWTLicense(String licenseKey, LicenseContext context) { private boolean verifyJWTLicense(String licenseKey) {
try { try {
log.info("Verifying ED25519_SIGN format license key"); log.info("Verifying ED25519_SIGN format license key");
@ -310,7 +291,7 @@ public class KeygenLicenseVerifier {
String payload = new String(payloadBytes); String payload = new String(payloadBytes);
// Process the license payload // Process the license payload
boolean isValid = processJWTLicensePayload(payload, context); boolean isValid = processJWTLicensePayload(payload);
return isValid; return isValid;
} catch (Exception e) { } catch (Exception e) {
@ -346,7 +327,7 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean processJWTLicensePayload(String payload, LicenseContext context) { private boolean processJWTLicensePayload(String payload) {
try { try {
log.info("Processing license payload: {}", payload); log.info("Processing license payload: {}", payload);
@ -367,13 +348,6 @@ public class KeygenLicenseVerifier {
String licenseId = licenseObj.optString("id", "unknown"); String licenseId = licenseObj.optString("id", "unknown");
log.info("Processing license with ID: {}", licenseId); log.info("Processing license with ID: {}", licenseId);
// Check for floating license in license object
context.isFloatingLicense = licenseObj.optBoolean("floating", false);
context.maxMachines = licenseObj.optInt("maxMachines", 1);
if (context.isFloatingLicense) {
log.info("Detected floating license with max machines: {}", context.maxMachines);
}
// Check expiry date // Check expiry date
String expiryStr = licenseObj.optString("expiry", null); String expiryStr = licenseObj.optString("expiry", null);
if (expiryStr != null && !"null".equals(expiryStr)) { if (expiryStr != null && !"null".equals(expiryStr)) {
@ -409,22 +383,9 @@ public class KeygenLicenseVerifier {
String policyId = policyObj.optString("id", "unknown"); String policyId = policyObj.optString("id", "unknown");
log.info("License uses policy: {}", policyId); log.info("License uses policy: {}", policyId);
// Check for floating license in policy
boolean policyFloating = policyObj.optBoolean("floating", false);
int policyMaxMachines = policyObj.optInt("maxMachines", 1);
// Policy settings take precedence
if (policyFloating) {
context.isFloatingLicense = true;
context.maxMachines = policyMaxMachines;
log.info(
"Policy defines floating license with max machines: {}",
context.maxMachines);
}
// Extract max users and isEnterprise from policy or metadata // Extract max users and isEnterprise from policy or metadata
int users = policyObj.optInt("users", 1); int users = policyObj.optInt("users", 0);
context.isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false); isEnterpriseLicense = policyObj.optBoolean("isEnterprise", false);
if (users > 0) { if (users > 0) {
applicationProperties.getPremium().setMaxUsers(users); applicationProperties.getPremium().setMaxUsers(users);
@ -438,7 +399,7 @@ public class KeygenLicenseVerifier {
log.info("License allows for {} users (from metadata)", users); log.info("License allows for {} users (from metadata)", users);
// Check for isEnterprise flag in metadata // Check for isEnterprise flag in metadata
context.isEnterpriseLicense = metadata.optBoolean("isEnterprise", false); isEnterpriseLicense = metadata.optBoolean("isEnterprise", false);
} else { } else {
// Default value // Default value
applicationProperties.getPremium().setMaxUsers(1); applicationProperties.getPremium().setMaxUsers(1);
@ -454,13 +415,13 @@ public class KeygenLicenseVerifier {
} }
} }
private boolean verifyStandardLicense(String licenseKey, LicenseContext context) { private boolean verifyStandardLicense(String licenseKey) {
try { try {
log.info("Checking standard license key"); log.info("Checking standard license key");
String machineFingerprint = generateMachineFingerprint(); String machineFingerprint = generateMachineFingerprint();
// First, try to validate the license // First, try to validate the license
JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint, context); JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint);
if (validationResponse != null) { if (validationResponse != null) {
boolean isValid = validationResponse.path("meta").path("valid").asBoolean(); boolean isValid = validationResponse.path("meta").path("valid").asBoolean();
String licenseId = validationResponse.path("data").path("id").asText(); String licenseId = validationResponse.path("data").path("id").asText();
@ -474,11 +435,10 @@ public class KeygenLicenseVerifier {
"License not activated for this machine. Attempting to" "License not activated for this machine. Attempting to"
+ " activate..."); + " activate...");
boolean activated = boolean activated =
activateMachine(licenseKey, licenseId, machineFingerprint, context); activateMachine(licenseKey, licenseId, machineFingerprint);
if (activated) { if (activated) {
// Revalidate after activation // Revalidate after activation
validationResponse = validationResponse = validateLicense(licenseKey, machineFingerprint);
validateLicense(licenseKey, machineFingerprint, context);
isValid = isValid =
validationResponse != null validationResponse != null
&& validationResponse && validationResponse
@ -498,8 +458,9 @@ public class KeygenLicenseVerifier {
} }
} }
private JsonNode validateLicense( private JsonNode validateLicense(String licenseKey, String machineFingerprint)
String licenseKey, String machineFingerprint, LicenseContext context) throws Exception { throws Exception {
HttpClient client = HttpClient.newHttpClient();
String requestBody = String requestBody =
String.format( String.format(
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}", "{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
@ -518,8 +479,7 @@ public class KeygenLicenseVerifier {
.POST(HttpRequest.BodyPublishers.ofString(requestBody)) .POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build(); .build();
HttpResponse<String> response = HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("ValidateLicenseResponse body: {}", response.body()); log.info("ValidateLicenseResponse body: {}", response.body());
JsonNode jsonResponse = objectMapper.readTree(response.body()); JsonNode jsonResponse = objectMapper.readTree(response.body());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
@ -533,61 +493,18 @@ public class KeygenLicenseVerifier {
log.info("Validation detail: " + detail); log.info("Validation detail: " + detail);
log.info("Validation code: " + code); log.info("Validation code: " + code);
// Check if the license itself has floating attribute // Extract user count
JsonNode licenseAttrs = jsonResponse.path("data").path("attributes");
if (!licenseAttrs.isMissingNode()) {
context.isFloatingLicense = licenseAttrs.path("floating").asBoolean(false);
context.maxMachines = licenseAttrs.path("maxMachines").asInt(1);
log.info(
"License floating (from license): {}, maxMachines: {}",
context.isFloatingLicense,
context.maxMachines);
}
// Also check the policy for floating license support if included
JsonNode includedNode = jsonResponse.path("included");
JsonNode policyNode = null;
if (includedNode.isArray()) {
for (JsonNode node : includedNode) {
if ("policies".equals(node.path("type").asText())) {
policyNode = node;
break;
}
}
}
if (policyNode != null) {
// Check if this is a floating license from policy
boolean policyFloating =
policyNode.path("attributes").path("floating").asBoolean(false);
int policyMaxMachines = policyNode.path("attributes").path("maxMachines").asInt(1);
// Policy takes precedence over license attributes
if (policyFloating) {
context.isFloatingLicense = true;
context.maxMachines = policyMaxMachines;
}
log.info(
"License floating (from policy): {}, maxMachines: {}",
context.isFloatingLicense,
context.maxMachines);
}
// Extract user count, default to 1 if not specified
int users = int users =
jsonResponse jsonResponse
.path("data") .path("data")
.path("attributes") .path("attributes")
.path("metadata") .path("metadata")
.path("users") .path("users")
.asInt(1); .asInt(0);
applicationProperties.getPremium().setMaxUsers(users); applicationProperties.getPremium().setMaxUsers(users);
// Extract isEnterprise flag // Extract isEnterprise flag
context.isEnterpriseLicense = isEnterpriseLicense =
jsonResponse jsonResponse
.path("data") .path("data")
.path("attributes") .path("attributes")
@ -603,105 +520,10 @@ public class KeygenLicenseVerifier {
return jsonResponse; return jsonResponse;
} }
private boolean activateMachine( private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint)
String licenseKey, String licenseId, String machineFingerprint, LicenseContext context)
throws Exception { throws Exception {
// For floating licenses, we first need to check if we need to deregister any machines HttpClient client = HttpClient.newHttpClient();
if (context.isFloatingLicense) {
log.info(
"Processing floating license activation. Max machines allowed: {}",
context.maxMachines);
// Get the current machines for this license
JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId);
if (machinesResponse != null) {
JsonNode machines = machinesResponse.path("data");
int currentMachines = machines.size();
log.info(
"Current machine count: {}, Max allowed: {}",
currentMachines,
context.maxMachines);
// Check if the current fingerprint is already activated
boolean isCurrentMachineActivated = false;
String currentMachineId = null;
for (JsonNode machine : machines) {
if (machineFingerprint.equals(
machine.path("attributes").path("fingerprint").asText())) {
isCurrentMachineActivated = true;
currentMachineId = machine.path("id").asText();
log.info(
"Current machine is already activated with ID: {}",
currentMachineId);
break;
}
}
// If the current machine is already activated, there's no need to do anything
if (isCurrentMachineActivated) {
log.info("Machine already activated. No action needed.");
return true;
}
// If we've reached the max machines limit, we need to deregister the oldest machine
if (currentMachines >= context.maxMachines) {
log.info(
"Max machines reached. Deregistering oldest machine to make room for the new machine.");
// Find the oldest machine based on creation timestamp
if (machines.size() > 0) {
// Find the machine with the oldest creation date
String oldestMachineId = null;
java.time.Instant oldestTime = null;
for (JsonNode machine : machines) {
String createdStr =
machine.path("attributes").path("created").asText(null);
if (createdStr != null && !createdStr.isEmpty()) {
try {
java.time.Instant createdTime =
java.time.Instant.parse(createdStr);
if (oldestTime == null || createdTime.isBefore(oldestTime)) {
oldestTime = createdTime;
oldestMachineId = machine.path("id").asText();
}
} catch (Exception e) {
log.warn(
"Could not parse creation time for machine: {}",
e.getMessage());
}
}
}
// If we couldn't determine the oldest by timestamp, use the first one
if (oldestMachineId == null) {
log.warn(
"Could not determine oldest machine by timestamp, using first machine in list");
oldestMachineId = machines.path(0).path("id").asText();
}
log.info("Deregistering machine with ID: {}", oldestMachineId);
boolean deregistered = deregisterMachine(licenseKey, oldestMachineId);
if (!deregistered) {
log.error(
"Failed to deregister machine. Cannot proceed with activation.");
return false;
}
log.info(
"Machine deregistered successfully. Proceeding with activation of new machine.");
} else {
log.error(
"License has reached machine limit but no machines were found to deregister. This is unexpected.");
// We'll still try to activate, but it might fail
}
}
}
}
// Proceed with machine activation
String hostname; String hostname;
try { try {
hostname = java.net.InetAddress.getLocalHost().getHostName(); hostname = java.net.InetAddress.getLocalHost().getHostName();
@ -748,8 +570,7 @@ public class KeygenLicenseVerifier {
.POST(HttpRequest.BodyPublishers.ofString(body.toString())) .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build(); .build();
HttpResponse<String> response = HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("activateMachine Response body: " + response.body()); log.info("activateMachine Response body: " + response.body());
if (response.statusCode() == 201) { if (response.statusCode() == 201) {
log.info("Machine activated successfully"); log.info("Machine activated successfully");
@ -767,81 +588,4 @@ public class KeygenLicenseVerifier {
private String generateMachineFingerprint() { private String generateMachineFingerprint() {
return GeneralUtils.generateMachineFingerprint(); return GeneralUtils.generateMachineFingerprint();
} }
/**
* Fetches all machines associated with a specific license
*
* @param licenseKey The license key to check
* @param licenseId The license ID
* @return JsonNode containing the list of machines, or null if an error occurs
* @throws Exception if an error occurs during the HTTP request
*/
private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception {
HttpRequest request =
HttpRequest.newBuilder()
.uri(
URI.create(
BASE_URL
+ "/"
+ ACCOUNT_ID
+ "/licenses/"
+ licenseId
+ "/machines"))
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.header("Authorization", "License " + licenseKey)
.GET()
.build();
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("fetchMachinesForLicense Response body: {}", response.body());
if (response.statusCode() == 200) {
return objectMapper.readTree(response.body());
} else {
log.error(
"Error fetching machines for license. Status code: {}, error: {}",
response.statusCode(),
response.body());
return null;
}
}
/**
* Deregisters a machine from a license
*
* @param licenseKey The license key
* @param machineId The ID of the machine to deregister
* @return true if deregistration was successful, false otherwise
*/
private boolean deregisterMachine(String licenseKey, String machineId) {
try {
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.header("Authorization", "License " + licenseKey)
.DELETE()
.build();
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 204) {
log.info("Machine {} successfully deregistered", machineId);
return true;
} else {
log.error(
"Error deregistering machine. Status code: {}, error: {}",
response.statusCode(),
response.body());
return false;
}
} catch (Exception e) {
log.error("Exception during machine deregistration: {}", e.getMessage(), e);
return false;
}
}
} }

View File

@ -31,8 +31,7 @@ public class LibreOfficeListener {
log.info("waiting for listener to start"); log.info("waiting for listener to start");
try (Socket socket = new Socket()) { try (Socket socket = new Socket()) {
socket.connect( socket.connect(
new InetSocketAddress("localhost", LISTENER_PORT), new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
1000); // Timeout after 1 second
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;

View File

@ -11,11 +11,8 @@ import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
import org.thymeleaf.templateresource.FileTemplateResource; import org.thymeleaf.templateresource.FileTemplateResource;
import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.ITemplateResource;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.InputStreamTemplateResource; import stirling.software.SPDF.model.InputStreamTemplateResource;
@Slf4j
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver { public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
@ -43,8 +40,7 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe
return new FileTemplateResource(resource.getFile().getPath(), characterEncoding); return new FileTemplateResource(resource.getFile().getPath(), characterEncoding);
} }
} catch (IOException e) { } catch (IOException e) {
// Log the exception to help with debugging issues loading external templates
log.warn("Unable to read template '{}' from file system", resourceName, e);
} }
InputStream inputStream = InputStream inputStream =

View File

@ -73,7 +73,7 @@ public class InitialSetup {
// Initialize Terms and Conditions // Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) { if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms"; String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl); GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl); applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
} }

View File

@ -15,7 +15,7 @@ public class MetricsConfig {
return new MeterFilter() { return new MeterFilter() {
@Override @Override
public MeterFilterReply accept(Meter.Id id) { public MeterFilterReply accept(Meter.Id id) {
if ("http.requests".equals(id.getName())) { if (id.getName().equals("http.requests")) {
return MeterFilterReply.NEUTRAL; return MeterFilterReply.NEUTRAL;
} }
return MeterFilterReply.DENY; return MeterFilterReply.DENY;

View File

@ -5,9 +5,7 @@ import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
@ -33,25 +31,14 @@ public class OpenApiConfig {
// default version if all else fails // default version if all else fails
version = "1.0.0"; version = "1.0.0";
} }
Info info = if (!applicationProperties.getSecurity().getEnableLogin()) {
return new OpenAPI()
.components(new Components())
.info(
new Info() new Info()
.title(DEFAULT_TITLE) .title(DEFAULT_TITLE)
.version(version) .version(version)
.license( .description(DEFAULT_DESCRIPTION));
new License()
.name("MIT")
.url(
"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE")
.identifier("MIT"))
.termsOfService("https://www.stirlingpdf.com/terms")
.contact(
new Contact()
.name("Stirling Software")
.url("https://www.stirlingpdf.com")
.email("contact@stirlingpdf.com"))
.description(DEFAULT_DESCRIPTION);
if (!applicationProperties.getSecurity().getEnableLogin()) {
return new OpenAPI().components(new Components()).info(info);
} else { } else {
SecurityScheme apiKeyScheme = SecurityScheme apiKeyScheme =
new SecurityScheme() new SecurityScheme()
@ -60,7 +47,11 @@ public class OpenApiConfig {
.name("X-API-KEY"); .name("X-API-KEY");
return new OpenAPI() return new OpenAPI()
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
.info(info) .info(
new Info()
.title(DEFAULT_TITLE)
.version(version)
.description(DEFAULT_DESCRIPTION))
.addSecurityItem(new SecurityRequirement().addList("apiKey")); .addSecurityItem(new SecurityRequirement().addList("apiKey"));
} }
} }

View File

@ -37,21 +37,8 @@ public class EmailService {
*/ */
@Async @Async
public void sendEmailWithAttachment(Email email) throws MessagingException { public void sendEmailWithAttachment(Email email) throws MessagingException {
MultipartFile file = email.getFileInput();
// 1) Validate recipient email address
if (email.getTo() == null || email.getTo().trim().isEmpty()) {
throw new MessagingException("Invalid Addresses");
}
// 2) Validate attachment
if (file == null
|| file.isEmpty()
|| file.getOriginalFilename() == null
|| file.getOriginalFilename().isEmpty()) {
throw new MessagingException("An attachment is required to send the email.");
}
ApplicationProperties.Mail mailProperties = applicationProperties.getMail(); ApplicationProperties.Mail mailProperties = applicationProperties.getMail();
MultipartFile file = email.getFileInput();
// Creates a MimeMessage to represent the email // Creates a MimeMessage to represent the email
MimeMessage message = mailSender.createMimeMessage(); MimeMessage message = mailSender.createMimeMessage();

View File

@ -59,8 +59,7 @@ public class AnalysisController {
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO") description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file) public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file)
throws IOException { throws IOException {
// Load the document in read-only mode to prevent modifications and ensure the integrity of // Load the document in read-only mode to prevent modifications and ensure the integrity of the original file.
// the original file.
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput(), true)) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput(), true)) {
PDDocumentInformation info = document.getDocumentInformation(); PDDocumentInformation info = document.getDocumentInformation();
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
@ -181,8 +180,7 @@ public class AnalysisController {
// Get permissions // Get permissions
Map<String, Boolean> permissions = new HashMap<>(); Map<String, Boolean> permissions = new HashMap<>();
permissions.put( permissions.put("preventPrinting", !document.getCurrentAccessPermission().canPrint());
"preventPrinting", !document.getCurrentAccessPermission().canPrint());
permissions.put( permissions.put(
"preventModify", !document.getCurrentAccessPermission().canModify()); "preventModify", !document.getCurrentAccessPermission().canModify());
permissions.put( permissions.put(

View File

@ -39,8 +39,8 @@ public class CropController {
description = description =
"This operation takes an input PDF file and crops it according to the given" "This operation takes an input PDF file and crops it according to the given"
+ " coordinates. Input:PDF Output:PDF Type:SISO") + " coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm request) throws IOException { public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = pdfDocumentFactory.load(request); PDDocument sourceDocument = pdfDocumentFactory.load(form);
PDDocument newDocument = PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
@ -64,8 +64,7 @@ public class CropController {
contentStream.saveGraphicsState(); contentStream.saveGraphicsState();
// Define the crop area // Define the crop area
contentStream.addRect( contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight());
request.getX(), request.getY(), request.getWidth(), request.getHeight());
contentStream.clip(); contentStream.clip();
// Draw the entire formXObject // Draw the entire formXObject
@ -77,11 +76,7 @@ public class CropController {
// Now, set the new page's media box to the cropped size // Now, set the new page's media box to the cropped size
newPage.setMediaBox( newPage.setMediaBox(
new PDRectangle( new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight()));
request.getX(),
request.getY(),
request.getWidth(),
request.getHeight()));
} }
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
@ -92,7 +87,7 @@ public class CropController {
byte[] pdfContent = baos.toByteArray(); byte[] pdfContent = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
pdfContent, pdfContent,
request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_cropped.pdf"); + "_cropped.pdf");
} }
} }

View File

@ -3,13 +3,11 @@ package stirling.software.SPDF.controller.api;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mail.MailSendException;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
@ -43,22 +41,11 @@ public class EmailController {
* @return ResponseEntity with success or error message. * @return ResponseEntity with success or error message.
*/ */
@PostMapping(consumes = "multipart/form-data", value = "/send-email") @PostMapping(consumes = "multipart/form-data", value = "/send-email")
@Operation(
summary = "Send an email with an attachment",
description =
"This endpoint sends an email with an attachment. Input:PDF"
+ " Output:Success/Failure Type:MISO")
public ResponseEntity<String> sendEmailWithAttachment(@Valid @ModelAttribute Email email) { public ResponseEntity<String> sendEmailWithAttachment(@Valid @ModelAttribute Email email) {
log.info("Sending email to: {}", email.toString());
try { try {
// Calls the service to send the email with attachment // Calls the service to send the email with attachment
emailService.sendEmailWithAttachment(email); emailService.sendEmailWithAttachment(email);
return ResponseEntity.ok("Email sent successfully"); return ResponseEntity.ok("Email sent successfully");
} catch (MailSendException ex) {
// handles your "Invalid Addresses" case
String errorMsg = ex.getMessage();
log.error("MailSendException: {}", errorMsg, ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMsg);
} catch (MessagingException e) { } catch (MessagingException e) {
// Catches any messaging exception (e.g., invalid email address, SMTP server issues) // Catches any messaging exception (e.g., invalid email address, SMTP server issues)
String errorMsg = "Failed to send email: " + e.getMessage(); String errorMsg = "Failed to send email: " + e.getMessage();

View File

@ -117,20 +117,20 @@ public class MergeController {
"This endpoint merges multiple PDF files into a single PDF file. The merged" "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" + " file will contain all pages from the input files in the order they were"
+ " provided. Input:PDF Output:PDF Type:MISO") + " provided. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest request) public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
throws IOException { throws IOException {
List<File> filesToDelete = new ArrayList<>(); // List of temporary files to delete List<File> filesToDelete = new ArrayList<>(); // List of temporary files to delete
File mergedTempFile = null; File mergedTempFile = null;
PDDocument mergedDocument = null; PDDocument mergedDocument = null;
boolean removeCertSign = Boolean.TRUE.equals(request.getRemoveCertSign()); boolean removeCertSign = form.isRemoveCertSign();
try { try {
MultipartFile[] files = request.getFileInput(); MultipartFile[] files = form.getFileInput();
Arrays.sort( Arrays.sort(
files, files,
getSortComparator( getSortComparator(
request.getSortType())); // Sort files based on the given sort type form.getSortType())); // Sort files based on the given sort type
PDFMergerUtility mergerUtility = new PDFMergerUtility(); PDFMergerUtility mergerUtility = new PDFMergerUtility();
long totalSize = 0; long totalSize = 0;

View File

@ -47,7 +47,7 @@ public class MultiPageLayoutController {
int pagesPerSheet = request.getPagesPerSheet(); int pagesPerSheet = request.getPagesPerSheet();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean addBorder = Boolean.TRUE.equals(request.getAddBorder()); boolean addBorder = request.isAddBorder();
if (pagesPerSheet != 2 if (pagesPerSheet != 2
&& pagesPerSheet != 3 && pagesPerSheet != 3

View File

@ -127,7 +127,7 @@ public class SplitPdfByChaptersController {
Path zipFile = null; Path zipFile = null;
try { try {
boolean includeMetadata = Boolean.TRUE.equals(request.getIncludeMetadata()); boolean includeMetadata = request.getIncludeMetadata();
Integer bookmarkLevel = Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks) request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) { if (bookmarkLevel < 0) {
@ -161,7 +161,7 @@ public class SplitPdfByChaptersController {
.body("Unable to extract outline items".getBytes()); .body("Unable to extract outline items".getBytes());
} }
boolean allowDuplicates = Boolean.TRUE.equals(request.getAllowDuplicates()); boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) { if (!allowDuplicates) {
/* /*
duplicates are generated when multiple bookmarks correspond to the same page, duplicates are generated when multiple bookmarks correspond to the same page,

View File

@ -60,7 +60,7 @@ public class SplitPdfBySectionsController {
// Process the PDF based on split parameters // Process the PDF based on split parameters
int horiz = request.getHorizontalDivisions() + 1; int horiz = request.getHorizontalDivisions() + 1;
int verti = request.getVerticalDivisions() + 1; int verti = request.getVerticalDivisions() + 1;
boolean merge = Boolean.TRUE.equals(request.getMerge()); boolean merge = request.isMerge();
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz); List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
String filename = String filename =

View File

@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -167,23 +168,13 @@ public class UserController {
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/updateUserSettings") @PostMapping("/updateUserSettings")
/** public String updateUserSettings(HttpServletRequest request, Principal principal)
* Updates the user settings based on the provided JSON payload.
*
* @param updates A map containing the settings to update. The expected structure is:
* <ul>
* <li><b>emailNotifications</b> (optional): "true" or "false" - Enable or disable email notifications.</li>
* <li><b>theme</b> (optional): "light" or "dark" - Set the user's preferred theme.</li>
* <li><b>language</b> (optional): A string representing the preferred language (e.g., "en", "fr").</li>
* </ul>
* Keys not listed above will be ignored.
* @param principal The currently authenticated user.
* @return A redirect string to the account page after updating the settings.
* @throws SQLException If a database error occurs.
* @throws UnsupportedProviderException If the operation is not supported for the user's provider.
*/
public String updateUserSettings(@RequestBody Map<String, String> updates, Principal principal)
throws SQLException, UnsupportedProviderException { throws SQLException, UnsupportedProviderException {
Map<String, String[]> paramMap = request.getParameterMap();
Map<String, String> updates = new HashMap<>();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
updates.put(entry.getKey(), entry.getValue()[0]);
}
log.debug("Processed updates: {}", updates); log.debug("Processed updates: {}", updates);
// Assuming you have a method in userService to update the settings for a user // Assuming you have a method in userService to update the settings for a user
userService.updateUserSettings(principal.getName(), updates); userService.updateUserSettings(principal.getName(), updates);

View File

@ -58,7 +58,7 @@ public class ConvertImgPDFController {
String imageFormat = request.getImageFormat(); String imageFormat = request.getImageFormat();
String singleOrMultiple = request.getSingleOrMultiple(); String singleOrMultiple = request.getSingleOrMultiple();
String colorType = request.getColorType(); String colorType = request.getColorType();
int dpi = request.getDpi(); String dpi = request.getDpi();
String pageNumbers = request.getPageNumbers(); String pageNumbers = request.getPageNumbers();
Path tempFile = null; Path tempFile = null;
Path tempOutputDir = null; Path tempOutputDir = null;
@ -94,7 +94,7 @@ public class ConvertImgPDFController {
: imageFormat.toUpperCase(), : imageFormat.toUpperCase(),
colorTypeResult, colorTypeResult,
singleImage, singleImage,
dpi, Integer.valueOf(dpi),
filename); filename);
if (result == null || result.length == 0) { if (result == null || result.length == 0) {
log.error("resultant bytes for {} is null, error converting ", filename); log.error("resultant bytes for {} is null, error converting ", filename);
@ -132,7 +132,7 @@ public class ConvertImgPDFController {
command.add(tempOutputDir.toString()); command.add(tempOutputDir.toString());
} }
command.add("--dpi"); command.add("--dpi");
command.add(String.valueOf(dpi)); command.add(dpi);
ProcessExecutorResult resultProcess = ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
@ -213,7 +213,7 @@ public class ConvertImgPDFController {
MultipartFile[] file = request.getFileInput(); MultipartFile[] file = request.getFileInput();
String fitOption = request.getFitOption(); String fitOption = request.getFitOption();
String colorType = request.getColorType(); String colorType = request.getColorType();
boolean autoRotate = Boolean.TRUE.equals(request.getAutoRotate()); boolean autoRotate = request.isAutoRotate();
// Handle Null entries for formdata // Handle Null entries for formdata
if (colorType == null || colorType.isBlank()) { if (colorType == null || colorType.isBlank()) {
colorType = "color"; colorType = "color";

View File

@ -47,9 +47,9 @@ public class ConvertMarkdownToPdf {
description = description =
"This endpoint takes a Markdown file input, converts it to HTML, and then to" "This endpoint takes a Markdown file input, converts it to HTML, and then to"
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO") + " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile) public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request)
throws Exception { throws Exception {
MultipartFile fileInput = generalFile.getFileInput(); MultipartFile fileInput = request.getFileInput();
if (fileInput == null) { if (fileInput == null) {
throw new IllegalArgumentException("Please provide a Markdown file for conversion."); throw new IllegalArgumentException("Please provide a Markdown file for conversion.");

View File

@ -90,9 +90,9 @@ public class ConvertOfficeController {
description = description =
"This endpoint converts a given file to a PDF using LibreOffice API Input:ANY" "This endpoint converts a given file to a PDF using LibreOffice API Input:ANY"
+ " Output:PDF Type:SISO") + " Output:PDF Type:SISO")
public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile generalFile) public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile request)
throws Exception { throws Exception {
MultipartFile inputFile = generalFile.getFileInput(); MultipartFile inputFile = request.getFileInput();
// unused but can start server instance if startup time is to long // unused but can start server instance if startup time is to long
// LibreOfficeListener.getInstance().start(); // LibreOfficeListener.getInstance().start();
File file = null; File file = null;

View File

@ -23,8 +23,9 @@ public class ConvertPDFToHtml {
summary = "Convert PDF to HTML", summary = "Convert PDF to HTML",
description = description =
"This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO") "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile file) throws Exception { public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile request)
MultipartFile inputFile = file.getFileInput(); throws Exception {
MultipartFile inputFile = request.getFileInput();
PDFToFile pdfToFile = new PDFToFile(); PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToHtml(inputFile); return pdfToFile.processPdfToHtml(inputFile);
} }

View File

@ -97,8 +97,9 @@ public class ConvertPDFToOffice {
description = description =
"This endpoint converts a PDF file to an XML file. Input:PDF Output:XML" "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML"
+ " Type:SISO") + " Type:SISO")
public ResponseEntity<byte[]> processPdfToXML(@ModelAttribute PDFFile file) throws Exception { public ResponseEntity<byte[]> processPdfToXML(@ModelAttribute PDFFile request)
MultipartFile inputFile = file.getFileInput(); throws Exception {
MultipartFile inputFile = request.getFileInput();
PDFToFile pdfToFile = new PDFToFile(); PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");

View File

@ -52,12 +52,12 @@ public class ExtractCSVController {
description = description =
"This operation takes an input PDF file and returns CSV file of whole page." "This operation takes an input PDF file and returns CSV file of whole page."
+ " Input:PDF Output:CSV Type:SISO") + " Input:PDF Output:CSV Type:SISO")
public ResponseEntity<?> pdfToCsv(@ModelAttribute PDFWithPageNums request) throws Exception { public ResponseEntity<?> pdfToCsv(@ModelAttribute PDFWithPageNums form) throws Exception {
String baseName = getBaseName(request.getFileInput().getOriginalFilename()); String baseName = getBaseName(form.getFileInput().getOriginalFilename());
List<CsvEntry> csvEntries = new ArrayList<>(); List<CsvEntry> csvEntries = new ArrayList<>();
try (PDDocument document = pdfDocumentFactory.load(request)) { try (PDDocument document = pdfDocumentFactory.load(form)) {
List<Integer> pages = request.getPageNumbersList(document, true); List<Integer> pages = form.getPageNumbersList(document, true);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
CSVFormat format = CSVFormat format =
CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build(); CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();

View File

@ -77,7 +77,7 @@ public class FilterController {
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request) public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
int pageCount = request.getPageCount(); String pageCount = request.getPageCount();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = pdfDocumentFactory.load(inputFile); PDDocument document = pdfDocumentFactory.load(inputFile);
@ -87,13 +87,13 @@ public class FilterController {
// Perform the comparison // Perform the comparison
switch (comparator) { switch (comparator) {
case "Greater": case "Greater":
valid = actualPageCount > pageCount; valid = actualPageCount > Integer.parseInt(pageCount);
break; break;
case "Equal": case "Equal":
valid = actualPageCount == pageCount; valid = actualPageCount == Integer.parseInt(pageCount);
break; break;
case "Less": case "Less":
valid = actualPageCount < pageCount; valid = actualPageCount < Integer.parseInt(pageCount);
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw new IllegalArgumentException("Invalid comparator: " + comparator);
@ -153,7 +153,7 @@ public class FilterController {
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request) public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
long fileSize = request.getFileSize(); String fileSize = request.getFileSize();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Get the file size // Get the file size
@ -163,13 +163,13 @@ public class FilterController {
// Perform the comparison // Perform the comparison
switch (comparator) { switch (comparator) {
case "Greater": case "Greater":
valid = actualFileSize > fileSize; valid = actualFileSize > Long.parseLong(fileSize);
break; break;
case "Equal": case "Equal":
valid = actualFileSize == fileSize; valid = actualFileSize == Long.parseLong(fileSize);
break; break;
case "Less": case "Less":
valid = actualFileSize < fileSize; valid = actualFileSize < Long.parseLong(fileSize);
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw new IllegalArgumentException("Invalid comparator: " + comparator);

View File

@ -47,7 +47,7 @@ public class AutoRenameController {
public ResponseEntity<byte[]> extractHeader(@ModelAttribute ExtractHeaderRequest request) public ResponseEntity<byte[]> extractHeader(@ModelAttribute ExtractHeaderRequest request)
throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean useFirstTextAsFallback = Boolean.TRUE.equals(request.getUseFirstTextAsFallback()); Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
PDDocument document = pdfDocumentFactory.load(file); PDDocument document = pdfDocumentFactory.load(file);
PDFTextStripper reader = PDFTextStripper reader =

View File

@ -113,7 +113,7 @@ public class AutoSplitPdfController {
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean duplexMode = Boolean.TRUE.equals(request.getDuplexMode()); boolean duplexMode = request.isDuplexMode();
PDDocument document = null; PDDocument document = null;
List<PDDocument> splitDocuments = new ArrayList<>(); List<PDDocument> splitDocuments = new ArrayList<>();

View File

@ -18,13 +18,14 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; 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.Operation;
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 io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -57,11 +58,24 @@ public class ExtractImageScansController {
+ " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP" + " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP"
+ " Type:SIMO") + " Type:SIMO")
public ResponseEntity<byte[]> extractImageScans( public ResponseEntity<byte[]> extractImageScans(
@ModelAttribute ExtractImageScansRequest request) @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 { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); String fileName = form.getFileInput().getOriginalFilename();
String fileName = inputFile.getOriginalFilename();
String extension = fileName.substring(fileName.lastIndexOf(".") + 1); String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
List<String> images = new ArrayList<>(); List<String> images = new ArrayList<>();
@ -80,7 +94,7 @@ public class ExtractImageScansController {
// Check if input file is a PDF // Check if input file is a PDF
if ("pdf".equalsIgnoreCase(extension)) { if ("pdf".equalsIgnoreCase(extension)) {
// Load PDF document // Load PDF document
try (PDDocument document = pdfDocumentFactory.load(inputFile)) { try (PDDocument document = pdfDocumentFactory.load(form.getFileInput())) {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true); pdfRenderer.setSubsamplingAllowed(true);
int pageCount = document.getNumberOfPages(); int pageCount = document.getNumberOfPages();
@ -102,7 +116,7 @@ public class ExtractImageScansController {
} }
} else { } else {
tempInputFile = Files.createTempFile("input_", "." + extension); tempInputFile = Files.createTempFile("input_", "." + extension);
inputFile.transferTo(tempInputFile); form.getFileInput().transferTo(tempInputFile);
// Add input file path to images list // Add input file path to images list
images.add(tempInputFile.toString()); images.add(tempInputFile.toString());
} }
@ -122,15 +136,15 @@ public class ExtractImageScansController {
images.get(i), images.get(i),
tempDir.toString(), tempDir.toString(),
"--angle_threshold", "--angle_threshold",
String.valueOf(request.getAngleThreshold()), String.valueOf(form.getAngleThreshold()),
"--tolerance", "--tolerance",
String.valueOf(request.getTolerance()), String.valueOf(form.getTolerance()),
"--min_area", "--min_area",
String.valueOf(request.getMinArea()), String.valueOf(form.getMinArea()),
"--min_contour_area", "--min_contour_area",
String.valueOf(request.getMinContourArea()), String.valueOf(form.getMinContourArea()),
"--border_size", "--border_size",
String.valueOf(request.getBorderSize()))); String.valueOf(form.getBorderSize())));
// Run CLI command // Run CLI command
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode =

View File

@ -64,7 +64,7 @@ public class ExtractImagesController {
throws IOException, InterruptedException, ExecutionException { throws IOException, InterruptedException, ExecutionException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String format = request.getFormat(); String format = request.getFormat();
boolean allowDuplicates = Boolean.TRUE.equals(request.getAllowDuplicates()); boolean allowDuplicates = request.isAllowDuplicates();
PDDocument document = pdfDocumentFactory.load(file); PDDocument document = pdfDocumentFactory.load(file);
// Determine if multithreading should be used based on PDF size or number of pages // Determine if multithreading should be used based on PDF size or number of pages

View File

@ -65,7 +65,7 @@ public class MetadataController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
// Extract metadata information // Extract metadata information
boolean deleteAll = Boolean.TRUE.equals(request.getDeleteAll()); Boolean deleteAll = request.isDeleteAll();
String author = request.getAuthor(); String author = request.getAuthor();
String creationDate = request.getCreationDate(); String creationDate = request.getCreationDate();
String creator = request.getCreator(); String creator = request.getCreator();

View File

@ -43,7 +43,7 @@ public class OverlayImageController {
MultipartFile imageFile = request.getImageFile(); MultipartFile imageFile = request.getImageFile();
float x = request.getX(); float x = request.getX();
float y = request.getY(); float y = request.getY();
boolean everyPage = Boolean.TRUE.equals(request.getEveryPage()); boolean everyPage = request.isEveryPage();
try { try {
byte[] pdfBytes = pdfFile.getBytes(); byte[] pdfBytes = pdfFile.getBytes();
byte[] imageBytes = imageFile.getBytes(); byte[] imageBytes = imageFile.getBytes();

View File

@ -49,30 +49,33 @@ public class PageNumbersController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String customMargin = request.getCustomMargin(); String customMargin = request.getCustomMargin();
int position = request.getPosition(); int position = request.getPosition();
int pageNumber = request.getStartingNumber(); int startingNumber = request.getStartingNumber();
String pagesToNumber = request.getPagesToNumber(); String pagesToNumber = request.getPagesToNumber();
String customText = request.getCustomText(); String customText = request.getCustomText();
float fontSize = request.getFontSize(); int pageNumber = startingNumber;
String fontType = request.getFontType();
PDDocument document = pdfDocumentFactory.load(file); PDDocument document = pdfDocumentFactory.load(file);
float font_size = request.getFontSize();
String font_type = request.getFontType();
float marginFactor; float marginFactor;
switch (customMargin.toLowerCase()) { switch (customMargin.toLowerCase()) {
case "small": case "small":
marginFactor = 0.02f; marginFactor = 0.02f;
break; break;
case "medium":
marginFactor = 0.035f;
break;
case "large": case "large":
marginFactor = 0.05f; marginFactor = 0.05f;
break; break;
case "x-large": case "x-large":
marginFactor = 0.075f; marginFactor = 0.075f;
break; break;
case "medium":
default: default:
marginFactor = 0.035f; marginFactor = 0.035f;
break; break;
} }
float fontSize = font_size;
if (pagesToNumber == null || pagesToNumber.isEmpty()) { if (pagesToNumber == null || pagesToNumber.isEmpty()) {
pagesToNumber = "all"; pagesToNumber = "all";
} }
@ -96,7 +99,7 @@ public class PageNumbersController {
.replaceFirst("[.][^.]+$", "")); .replaceFirst("[.][^.]+$", ""));
PDType1Font currentFont = PDType1Font currentFont =
switch (fontType.toLowerCase()) { switch (font_type.toLowerCase()) {
case "courier" -> new PDType1Font(Standard14Fonts.FontName.COURIER); case "courier" -> new PDType1Font(Standard14Fonts.FontName.COURIER);
case "times" -> new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN); case "times" -> new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
default -> new PDType1Font(Standard14Fonts.FontName.HELVETICA); default -> new PDType1Font(Standard14Fonts.FontName.HELVETICA);

View File

@ -40,9 +40,9 @@ public class RepairController {
"This endpoint repairs a given PDF file by running qpdf command. The PDF is" "This endpoint repairs a given PDF file by running qpdf command. The PDF is"
+ " first saved to a temporary location, repaired, read back, and then" + " first saved to a temporary location, repaired, read back, and then"
+ " returned as a response. Input:PDF Output:PDF Type:SISO") + " returned as a response. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile file) public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = request.getFileInput();
// Save the uploaded file to a temporary location // Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf"); Path tempInputFile = Files.createTempFile("input_", ".pdf");
byte[] pdfBytes = null; byte[] pdfBytes = null;

View File

@ -31,18 +31,18 @@ public class ReplaceAndInvertColorController {
@Operation( @Operation(
summary = "Replace-Invert Color PDF", summary = "Replace-Invert Color PDF",
description = description =
"This endpoint accepts a PDF file and option of invert all colors or replace" "This endpoint accepts a PDF file and option of invert all colors or replace text and background colors. Input:PDF Output:PDF Type:SISO")
+ " text and background colors. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<InputStreamResource> replaceAndInvertColor( public ResponseEntity<InputStreamResource> replaceAndInvertColor(
@ModelAttribute ReplaceAndInvertColorRequest request) throws IOException { @ModelAttribute ReplaceAndInvertColorRequest replaceAndInvertColorRequest)
throws IOException {
InputStreamResource resource = InputStreamResource resource =
replaceAndInvertColorService.replaceAndInvertColor( replaceAndInvertColorService.replaceAndInvertColor(
request.getFileInput(), replaceAndInvertColorRequest.getFileInput(),
request.getReplaceAndInvertOption(), replaceAndInvertColorRequest.getReplaceAndInvertOption(),
request.getHighContrastColorCombination(), replaceAndInvertColorRequest.getHighContrastColorCombination(),
request.getBackGroundColor(), replaceAndInvertColorRequest.getBackGroundColor(),
request.getTextColor()); replaceAndInvertColorRequest.getTextColor());
// Return the modified PDF as a downloadable file // Return the modified PDF as a downloadable file
return ResponseEntity.ok() return ResponseEntity.ok()

View File

@ -36,8 +36,8 @@ public class ShowJavascript {
@Operation( @Operation(
summary = "Grabs all JS from a PDF and returns a single JS file with all code", summary = "Grabs all JS from a PDF and returns a single JS file with all code",
description = "desc. Input:PDF Output:JS Type:SISO") description = "desc. Input:PDF Output:JS Type:SISO")
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile file) throws Exception { public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = request.getFileInput();
String script = ""; String script = "";
try (PDDocument document = pdfDocumentFactory.load(inputFile)) { try (PDDocument document = pdfDocumentFactory.load(inputFile)) {

View File

@ -93,7 +93,6 @@ public class PipelineProcessor {
ByteArrayOutputStream logStream = new ByteArrayOutputStream(); ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream); PrintStream logPrintStream = new PrintStream(logStream);
boolean hasErrors = false; boolean hasErrors = false;
boolean filtersApplied = false;
for (PipelineOperation pipelineOperation : config.getOperations()) { for (PipelineOperation pipelineOperation : config.getOperations()) {
String operation = pipelineOperation.getOperation(); String operation = pipelineOperation.getOperation();
boolean isMultiInputOperation = apiDocService.isMultiInput(operation); boolean isMultiInputOperation = apiDocService.isMultiInput(operation);
@ -135,7 +134,7 @@ public class PipelineProcessor {
if (operation.startsWith("filter-") if (operation.startsWith("filter-")
&& (response.getBody() == null && (response.getBody() == null
|| response.getBody().length == 0)) { || response.getBody().length == 0)) {
filtersApplied = true; result.setFiltersApplied(true);
log.info("Skipping file due to filtering {}", operation); log.info("Skipping file due to filtering {}", operation);
continue; continue;
} }
@ -216,12 +215,12 @@ public class PipelineProcessor {
log.error("Errors occurred during processing. Log: {}", logStream.toString()); log.error("Errors occurred during processing. Log: {}", logStream.toString());
} }
result.setHasErrors(hasErrors); result.setHasErrors(hasErrors);
result.setFiltersApplied(filtersApplied); result.setFiltersApplied(hasErrors);
result.setOutputFiles(outputFiles); result.setOutputFiles(outputFiles);
return result; return result;
} }
/* package */ ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) { private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) {
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
// Set up headers, including API key // Set up headers, including API key
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();

View File

@ -63,16 +63,14 @@ public class PasswordController {
String ownerPassword = request.getOwnerPassword(); String ownerPassword = request.getOwnerPassword();
String password = request.getPassword(); String password = request.getPassword();
int keyLength = request.getKeyLength(); int keyLength = request.getKeyLength();
boolean preventAssembly = Boolean.TRUE.equals(request.getPreventAssembly()); boolean preventAssembly = request.isPreventAssembly();
boolean preventExtractContent = Boolean.TRUE.equals(request.getPreventExtractContent()); boolean preventExtractContent = request.isPreventExtractContent();
boolean preventExtractForAccessibility = boolean preventExtractForAccessibility = request.isPreventExtractForAccessibility();
Boolean.TRUE.equals(request.getPreventExtractForAccessibility()); boolean preventFillInForm = request.isPreventFillInForm();
boolean preventFillInForm = Boolean.TRUE.equals(request.getPreventFillInForm()); boolean preventModify = request.isPreventModify();
boolean preventModify = Boolean.TRUE.equals(request.getPreventModify()); boolean preventModifyAnnotations = request.isPreventModifyAnnotations();
boolean preventModifyAnnotations = boolean preventPrinting = request.isPreventPrinting();
Boolean.TRUE.equals(request.getPreventModifyAnnotations()); boolean preventPrintingFaithful = request.isPreventPrintingFaithful();
boolean preventPrinting = Boolean.TRUE.equals(request.getPreventPrinting());
boolean preventPrintingFaithful = Boolean.TRUE.equals(request.getPreventPrintingFaithful());
PDDocument document = pdfDocumentFactory.load(fileInput); PDDocument document = pdfDocumentFactory.load(fileInput);
AccessPermission ap = new AccessPermission(); AccessPermission ap = new AccessPermission();

View File

@ -75,7 +75,7 @@ public class RedactController {
redactPages(request, document, allPages); redactPages(request, document, allPages);
redactAreas(redactionAreas, document, allPages); redactAreas(redactionAreas, document, allPages);
if (Boolean.TRUE.equals(request.getConvertPDFToImage())) { if (request.isConvertPDFToImage()) {
PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document); PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);
document.close(); document.close();
document = convertedPdf; document = convertedPdf;
@ -180,6 +180,7 @@ public class RedactController {
} }
} }
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) { private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
String pageNumbersInput = request.getPageNumbers(); String pageNumbersInput = request.getPageNumbers();
String[] parsedPageNumbers = String[] parsedPageNumbers =
@ -200,11 +201,11 @@ public class RedactController {
throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String listOfTextString = request.getListOfText(); String listOfTextString = request.getListOfText();
boolean useRegex = Boolean.TRUE.equals(request.getUseRegex()); boolean useRegex = request.isUseRegex();
boolean wholeWordSearchBool = Boolean.TRUE.equals(request.getWholeWordSearch()); boolean wholeWordSearchBool = request.isWholeWordSearch();
String colorString = request.getRedactColor(); String colorString = request.getRedactColor();
float customPadding = request.getCustomPadding(); float customPadding = request.getCustomPadding();
boolean convertPDFToImage = Boolean.TRUE.equals(request.getConvertPDFToImage()); boolean convertPDFToImage = request.isConvertPDFToImage();
String[] listOfText = listOfTextString.split("\n"); String[] listOfText = listOfTextString.split("\n");
PDDocument document = pdfDocumentFactory.load(file); PDDocument document = pdfDocumentFactory.load(file);

View File

@ -46,12 +46,12 @@ public class SanitizeController {
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request) public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
throws IOException { throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
boolean removeJavaScript = Boolean.TRUE.equals(request.getRemoveJavaScript()); boolean removeJavaScript = request.isRemoveJavaScript();
boolean removeEmbeddedFiles = Boolean.TRUE.equals(request.getRemoveEmbeddedFiles()); boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
boolean removeXMPMetadata = Boolean.TRUE.equals(request.getRemoveXMPMetadata()); boolean removeXMPMetadata = request.isRemoveXMPMetadata();
boolean removeMetadata = Boolean.TRUE.equals(request.getRemoveMetadata()); boolean removeMetadata = request.isRemoveMetadata();
boolean removeLinks = Boolean.TRUE.equals(request.getRemoveLinks()); boolean removeLinks = request.isRemoveLinks();
boolean removeFonts = Boolean.TRUE.equals(request.getRemoveFonts()); boolean removeFonts = request.isRemoveFonts();
PDDocument document = pdfDocumentFactory.load(inputFile, true); PDDocument document = pdfDocumentFactory.load(inputFile, true);
if (removeJavaScript) { if (removeJavaScript) {

View File

@ -1,6 +1,5 @@
package stirling.software.SPDF.controller.api.security; package stirling.software.SPDF.controller.api.security;
import java.beans.PropertyEditorSupport;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@ -24,8 +23,6 @@ import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.util.Store; import org.bouncycastle.util.Store;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -51,18 +48,6 @@ public class ValidateSignatureController {
private final CustomPDFDocumentFactory pdfDocumentFactory; private final CustomPDFDocumentFactory pdfDocumentFactory;
private final CertificateValidationService certValidationService; private final CertificateValidationService certValidationService;
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
MultipartFile.class,
new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(null);
}
});
}
@Operation( @Operation(
summary = "Validate PDF Digital Signature", summary = "Validate PDF Digital Signature",
description = description =
@ -73,12 +58,12 @@ public class ValidateSignatureController {
@ModelAttribute SignatureValidationRequest request) throws IOException { @ModelAttribute SignatureValidationRequest request) throws IOException {
List<SignatureValidationResult> results = new ArrayList<>(); List<SignatureValidationResult> results = new ArrayList<>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
MultipartFile certFile = request.getCertFile();
// Load custom certificate if provided // Load custom certificate if provided
X509Certificate customCert = null; X509Certificate customCert = null;
if (certFile != null && !certFile.isEmpty()) { if (request.getCertFile() != null && !request.getCertFile().isEmpty()) {
try (ByteArrayInputStream certStream = new ByteArrayInputStream(certFile.getBytes())) { try (ByteArrayInputStream certStream =
new ByteArrayInputStream(request.getCertFile().getBytes())) {
CertificateFactory cf = CertificateFactory.getInstance("X.509"); CertificateFactory cf = CertificateFactory.getInstance("X.509");
customCert = (X509Certificate) cf.generateCertificate(certStream); customCert = (X509Certificate) cf.generateCertificate(certStream);
} catch (CertificateException e) { } catch (CertificateException e) {

View File

@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.security;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.beans.PropertyEditorSupport;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -25,8 +24,6 @@ import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -52,18 +49,6 @@ public class WatermarkController {
private final CustomPDFDocumentFactory pdfDocumentFactory; private final CustomPDFDocumentFactory pdfDocumentFactory;
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
MultipartFile.class,
new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(null);
}
});
}
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark") @PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
@Operation( @Operation(
summary = "Add watermark to a PDF file", summary = "Add watermark to a PDF file",
@ -84,7 +69,7 @@ public class WatermarkController {
int widthSpacer = request.getWidthSpacer(); int widthSpacer = request.getWidthSpacer();
int heightSpacer = request.getHeightSpacer(); int heightSpacer = request.getHeightSpacer();
String customColor = request.getCustomColor(); String customColor = request.getCustomColor();
boolean convertPdfToImage = Boolean.TRUE.equals(request.getConvertPDFToImage()); boolean convertPdfToImage = request.isConvertPDFToImage();
// Load the input PDF // Load the input PDF
PDDocument document = pdfDocumentFactory.load(pdfFile); PDDocument document = pdfDocumentFactory.load(pdfFile);

View File

@ -77,8 +77,9 @@ public class HomeWebController {
} }
@GetMapping("/home-legacy") @GetMapping("/home-legacy")
public String redirectHomeLegacy() { public String homeLegacy(Model model) {
return "redirect:/"; model.addAttribute("currentPage", "home-legacy");
return "home-legacy";
} }
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)

View File

@ -1,6 +1,5 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -53,6 +52,6 @@ public class UploadLimitService {
if (bytes < 1024) return bytes + " B"; if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024)); int exp = (int) (Math.log(bytes) / Math.log(1024));
String pre = "KMGTPE".charAt(exp - 1) + "B"; String pre = "KMGTPE".charAt(exp - 1) + "B";
return String.format(Locale.US, "%.1f %s", bytes / Math.pow(1024, exp), pre); return String.format("%.1f %s", bytes / Math.pow(1024, exp), pre);
} }
} }

View File

@ -39,6 +39,7 @@ public class InputStreamTemplateResource implements ITemplateResource {
@Override @Override
public boolean exists() { public boolean exists() {
return inputStream != null; // TODO Auto-generated method stub
return false;
} }
} }

View File

@ -4,6 +4,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;

View File

@ -11,9 +11,6 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode @EqualsAndHashCode
public class GeneralFile { public class GeneralFile {
@Schema( @Schema(description = "The input file")
description = "The input file",
requiredMode = Schema.RequiredMode.REQUIRED,
format = "binary")
private MultipartFile fileInput; private MultipartFile fileInput;
} }

View File

@ -11,12 +11,9 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode @EqualsAndHashCode
public class HandleDataRequest { public class HandleDataRequest {
@Schema(description = "The input files", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "The input files")
private MultipartFile[] fileInput; private MultipartFile[] fileInput;
@Schema( @Schema(description = "JSON String")
description = "JSON String",
defaultValue = "{}",
requiredMode = Schema.RequiredMode.REQUIRED)
private String json; private String json;
} }

View File

@ -10,9 +10,6 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode @EqualsAndHashCode
public class ImageFile { public class ImageFile {
@Schema( @Schema(description = "The input image file")
description = "The input image file",
requiredMode = Schema.RequiredMode.REQUIRED,
format = "binary")
private MultipartFile fileInput; private MultipartFile fileInput;
} }

View File

@ -10,6 +10,6 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode @EqualsAndHashCode
public class MultiplePDFFiles { public class MultiplePDFFiles {
@Schema(description = "The input PDF files", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "The input PDF files", type = "array", format = "binary")
private MultipartFile[] fileInput; private MultipartFile[] fileInput;
} }

View File

@ -11,7 +11,6 @@ public class PDFComparison extends PDFFile {
@Schema( @Schema(
description = "The comparison type, accepts Greater, Equal, Less than", description = "The comparison type, accepts Greater, Equal, Less than",
allowableValues = {"Greater", "Equal", "Less"}, allowableValues = {"Greater", "Equal", "Less"})
requiredMode = Schema.RequiredMode.REQUIRED)
private String comparator; private String comparator;
} }

View File

@ -8,6 +8,6 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class PDFComparisonAndCount extends PDFComparison { public class PDFComparisonAndCount extends PDFComparison {
@Schema(description = "Count", requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "0") @Schema(description = "Count")
private int pageCount; private String pageCount;
} }

View File

@ -11,8 +11,6 @@ public class PDFExtractImagesRequest extends PDFWithImageFormatRequest {
@Schema( @Schema(
description = description =
"Boolean to enable/disable the saving of duplicate images, true to enable" "Boolean to enable/disable the saving of duplicate images, true to enable duplicates")
+ " duplicates", private boolean allowDuplicates;
defaultValue = "false")
private Boolean allowDuplicates;
} }

View File

@ -12,10 +12,6 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode @EqualsAndHashCode
public class PDFFile { public class PDFFile {
@Schema( @Schema(description = "The input PDF file", format = "binary")
description = "The input PDF file",
requiredMode = Schema.RequiredMode.REQUIRED,
contentMediaType = "application/pdf",
format = "binary")
private MultipartFile fileInput; private MultipartFile fileInput;
} }

View File

@ -11,8 +11,6 @@ public class PDFWithImageFormatRequest extends PDFFile {
@Schema( @Schema(
description = "The output image format e.g., 'png', 'jpeg', or 'gif'", description = "The output image format e.g., 'png', 'jpeg', or 'gif'",
allowableValues = {"png", "jpeg", "gif"}, allowableValues = {"png", "jpeg", "gif"})
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "png")
private String format; private String format;
} }

View File

@ -21,9 +21,9 @@ public class PDFWithPageNums extends PDFFile {
description = description =
"The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the" "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" + " 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')", + " constant (e.g., '2n+1', '3n', '6n-5')\"",
defaultValue = "all", defaultValue = "all",
requiredMode = RequiredMode.REQUIRED) requiredMode = RequiredMode.NOT_REQUIRED)
private String pageNumbers; private String pageNumbers;
@Hidden @Hidden

View File

@ -11,9 +11,7 @@ public class PDFWithPageSize extends PDFFile {
@Schema( @Schema(
description = description =
"The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER," "The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL, KEEP.",
+ " LEGAL, KEEP.",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL", "KEEP"}) allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL", "KEEP"})
private String pageSize; private String pageSize;
} }

View File

@ -8,22 +8,12 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class SplitPdfByChaptersRequest extends PDFFile { public class SplitPdfByChaptersRequest extends PDFFile {
@Schema( @Schema(description = "Whether to include Metadata or not", example = "true")
description = "Whether to include Metadata or not",
defaultValue = "true",
requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean includeMetadata; private Boolean includeMetadata;
@Schema( @Schema(description = "Whether to allow duplicates or not", example = "true")
description = "Whether to allow duplicates or not",
defaultValue = "true",
requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean allowDuplicates; private Boolean allowDuplicates;
@Schema( @Schema(description = "Maximum bookmark level required", example = "2")
description = "Maximum bookmark level required",
minimum = "0",
defaultValue = "2",
requiredMode = Schema.RequiredMode.REQUIRED)
private Integer bookmarkLevel; private Integer bookmarkLevel;
} }

View File

@ -8,23 +8,12 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class SplitPdfBySectionsRequest extends PDFFile { public class SplitPdfBySectionsRequest extends PDFFile {
@Schema( @Schema(description = "Number of horizontal divisions for each PDF page", example = "2")
description = "Number of horizontal divisions for each PDF page",
defaultValue = "0",
minimum = "0",
requiredMode = Schema.RequiredMode.REQUIRED)
private int horizontalDivisions; private int horizontalDivisions;
@Schema( @Schema(description = "Number of vertical divisions for each PDF page", example = "2")
description = "Number of vertical divisions for each PDF page",
defaultValue = "1",
minimum = "0",
requiredMode = Schema.RequiredMode.REQUIRED)
private int verticalDivisions; private int verticalDivisions;
@Schema( @Schema(description = "Merge the split documents into a single PDF", example = "true")
description = "Merge the split documents into a single PDF", private boolean merge;
defaultValue = "true",
requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean merge;
} }

View File

@ -23,9 +23,9 @@ public class ConvertPDFToMarkdown {
summary = "Convert PDF to Markdown", summary = "Convert PDF to Markdown",
description = description =
"This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO") "This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO")
public ResponseEntity<byte[]> processPdfToMarkdown(@ModelAttribute PDFFile file) public ResponseEntity<byte[]> processPdfToMarkdown(@ModelAttribute PDFFile request)
throws Exception { throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = request.getFileInput();
PDFToFile pdfToFile = new PDFToFile(); PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToMarkdown(inputFile); return pdfToFile.processPdfToMarkdown(inputFile);
} }

View File

@ -5,38 +5,33 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFFile;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ConvertToImageRequest extends PDFWithPageNums { public class ConvertToImageRequest extends PDFFile {
@Schema( @Schema(
description = "The output image format", description = "The output image format",
defaultValue = "png", allowableValues = {"png", "jpeg", "jpg", "gif", "webp"})
allowableValues = {"png", "jpeg", "jpg", "gif", "webp"},
requiredMode = Schema.RequiredMode.REQUIRED)
private String imageFormat; private String imageFormat;
@Schema( @Schema(
description = description =
"Choose between a single image containing all pages or separate images for each" "Choose between a single image containing all pages or separate images for each page",
+ " page", allowableValues = {"single", "multiple"})
defaultValue = "multiple",
allowableValues = {"single", "multiple"},
requiredMode = Schema.RequiredMode.REQUIRED)
private String singleOrMultiple; private String singleOrMultiple;
@Schema( @Schema(
description = "The color type of the output image(s)", description =
defaultValue = "color", "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')\"")
allowableValues = {"color", "greyscale", "blackwhite"}, private String pageNumbers;
requiredMode = Schema.RequiredMode.REQUIRED)
private String colorType;
@Schema( @Schema(
description = "The DPI (dots per inch) for the output image(s)", description = "The color type of the output image(s)",
defaultValue = "300", allowableValues = {"color", "greyscale", "blackwhite"})
requiredMode = Schema.RequiredMode.REQUIRED) private String colorType;
private Integer dpi;
@Schema(description = "The DPI (dots per inch) for the output image(s)")
private String dpi;
} }

View File

@ -11,28 +11,21 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode @EqualsAndHashCode
public class ConvertToPdfRequest { public class ConvertToPdfRequest {
@Schema( @Schema(description = "The input images to be converted to a PDF file")
description = "The input images to be converted to a PDF file",
requiredMode = Schema.RequiredMode.REQUIRED)
private MultipartFile[] fileInput; private MultipartFile[] fileInput;
@Schema( @Schema(
description = "Option to determine how the image will fit onto the page", description = "Option to determine how the image will fit onto the page",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "fillPage",
allowableValues = {"fillPage", "fitDocumentToImage", "maintainAspectRatio"}) allowableValues = {"fillPage", "fitDocumentToImage", "maintainAspectRatio"})
private String fitOption; private String fitOption;
@Schema( @Schema(
description = "The color type of the output image(s)", description = "The color type of the output image(s)",
defaultValue = "color",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"color", "greyscale", "blackwhite"}) allowableValues = {"color", "greyscale", "blackwhite"})
private String colorType; private String colorType;
@Schema( @Schema(
description = "Whether to automatically rotate the images to better fit the PDF page", description = "Whether to automatically rotate the images to better fit the PDF page",
requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
defaultValue = "false") private boolean autoRotate;
private Boolean autoRotate;
} }

View File

@ -13,7 +13,6 @@ public class HTMLToPdfRequest extends PDFFile {
@Schema( @Schema(
description = "Zoom level for displaying the website. Default is '1'.", description = "Zoom level for displaying the website. Default is '1'.",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "1") defaultValue = "1")
private float zoom; private float zoom;
} }

View File

@ -13,7 +13,6 @@ public class PdfToBookRequest extends PDFFile {
@Schema( @Schema(
description = "The output Ebook format", description = "The output Ebook format",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = { allowableValues = {
"epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb", "lrf" "epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb", "lrf"
}) })

View File

@ -13,7 +13,6 @@ public class PdfToPdfARequest extends PDFFile {
@Schema( @Schema(
description = "The output PDF/A type", description = "The output PDF/A type",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"pdfa", "pdfa-1"}) allowableValues = {"pdfa", "pdfa-1"})
private String outputFormat; private String outputFormat;
} }

View File

@ -13,7 +13,6 @@ public class PdfToPresentationRequest extends PDFFile {
@Schema( @Schema(
description = "The output Presentation format", description = "The output Presentation format",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"ppt", "pptx", "odp"}) allowableValues = {"ppt", "pptx", "odp"})
private String outputFormat; private String outputFormat;
} }

View File

@ -13,7 +13,6 @@ public class PdfToTextOrRTFRequest extends PDFFile {
@Schema( @Schema(
description = "The output Text or RTF format", description = "The output Text or RTF format",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"rtf", "txt"}) allowableValues = {"rtf", "txt"})
private String outputFormat; private String outputFormat;
} }

View File

@ -13,7 +13,6 @@ public class PdfToWordRequest extends PDFFile {
@Schema( @Schema(
description = "The output Word document format", description = "The output Word document format",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"doc", "docx", "odt"}) allowableValues = {"doc", "docx", "odt"})
private String outputFormat; private String outputFormat;
} }

View File

@ -11,9 +11,6 @@ import stirling.software.SPDF.model.api.PDFWithPageNums;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ContainsTextRequest extends PDFWithPageNums { public class ContainsTextRequest extends PDFWithPageNums {
@Schema( @Schema(description = "The text to check for", requiredMode = Schema.RequiredMode.REQUIRED)
description = "The text to check for",
defaultValue = "text",
requiredMode = Schema.RequiredMode.REQUIRED)
private String text; private String text;
} }

View File

@ -11,9 +11,6 @@ import stirling.software.SPDF.model.api.PDFComparison;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class FileSizeRequest extends PDFComparison { public class FileSizeRequest extends PDFComparison {
@Schema( @Schema(description = "File Size", requiredMode = Schema.RequiredMode.REQUIRED)
description = "Size of the file in bytes", private String fileSize;
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "0")
private long fileSize;
} }

View File

@ -11,9 +11,6 @@ import stirling.software.SPDF.model.api.PDFComparison;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class PageRotationRequest extends PDFComparison { public class PageRotationRequest extends PDFComparison {
@Schema( @Schema(description = "Rotation in degrees", requiredMode = Schema.RequiredMode.REQUIRED)
description = "Rotation in degrees",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "0")
private int rotation; private int rotation;
} }

View File

@ -11,10 +11,6 @@ import stirling.software.SPDF.model.api.PDFComparison;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class PageSizeRequest extends PDFComparison { public class PageSizeRequest extends PDFComparison {
@Schema( @Schema(description = "Standard Page Size", requiredMode = Schema.RequiredMode.REQUIRED)
description = "Standard Page Size",
allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL"},
defaultValue = "A4",
requiredMode = Schema.RequiredMode.REQUIRED)
private String standardPageSize; private String standardPageSize;
} }

View File

@ -13,12 +13,10 @@ public class MergeMultiplePagesRequest extends PDFFile {
@Schema( @Schema(
description = "The number of pages to fit onto a single sheet in the output PDF.", description = "The number of pages to fit onto a single sheet in the output PDF.",
type = "number", type = "integer",
defaultValue = "2",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"2", "3", "4", "9", "16"}) allowableValues = {"2", "3", "4", "9", "16"})
private int pagesPerSheet; private int pagesPerSheet;
@Schema(description = "Boolean for if you wish to add border around the pages") @Schema(description = "Boolean for if you wish to add border around the pages")
private Boolean addBorder; private boolean addBorder;
} }

View File

@ -20,16 +20,12 @@ public class MergePdfsRequest extends MultiplePDFFiles {
"byDateCreated", "byDateCreated",
"byPDFTitle" "byPDFTitle"
}, },
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "orderProvided") defaultValue = "orderProvided")
private String sortType = "orderProvided"; private String sortType = "orderProvided";
@Schema( @Schema(
description = description =
"Flag indicating whether to remove certification signatures from the merged" "Flag indicating whether to remove certification signatures from the merged PDF. If true, all certification signatures will be removed from the final merged document.",
+ " PDF. If true, all certification signatures will be removed from the" example = "true")
+ " final merged document.", private boolean isRemoveCertSign;
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "true")
private Boolean removeCertSign;
} }

View File

@ -15,32 +15,21 @@ public class OverlayPdfsRequest extends PDFFile {
@Schema( @Schema(
description = description =
"An array of PDF files to be used as overlays on the base PDF. The order in" "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.")
+ " these files is applied based on the selected mode.",
requiredMode = Schema.RequiredMode.REQUIRED)
private MultipartFile[] overlayFiles; private MultipartFile[] overlayFiles;
@Schema( @Schema(
description = description =
"The mode of overlaying: 'SequentialOverlay' for sequential application," "The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts",
+ " 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay'"
+ " for fixed repetition based on provided counts",
allowableValues = {"SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay"},
requiredMode = Schema.RequiredMode.REQUIRED) requiredMode = Schema.RequiredMode.REQUIRED)
private String overlayMode; private String overlayMode;
@Schema( @Schema(
description = description =
"An array of integers specifying the number of times each corresponding overlay" "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.",
+ " file should be applied in the 'FixedRepeatOverlay' mode. This should"
+ " match the length of the overlayFiles array.",
requiredMode = Schema.RequiredMode.NOT_REQUIRED) requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private int[] counts; private int[] counts;
@Schema( @Schema(description = "Overlay position 0 is Foregound, 1 is Background")
description = "Overlay position 0 is Foregound, 1 is Background",
allowableValues = {"0", "1"},
requiredMode = Schema.RequiredMode.REQUIRED,
type = "number")
private int overlayPosition; private int overlayPosition;
} }

View File

@ -14,8 +14,6 @@ public class RotatePDFRequest extends PDFFile {
@Schema( @Schema(
description = description =
"The angle by which to rotate the PDF file. This should be a multiple of 90.", "The angle by which to rotate the PDF file. This should be a multiple of 90.",
requiredMode = Schema.RequiredMode.REQUIRED, example = "90")
allowableValues = {"0", "90", "180", "270"},
defaultValue = "90")
private Integer angle; private Integer angle;
} }

View File

@ -12,11 +12,7 @@ import stirling.software.SPDF.model.api.PDFWithPageSize;
public class ScalePagesRequest extends PDFWithPageSize { public class ScalePagesRequest extends PDFWithPageSize {
@Schema( @Schema(
minimum = "0",
defaultValue = "1",
requiredMode = Schema.RequiredMode.REQUIRED,
description = description =
"The scale of the content on the pages of the output PDF. Acceptable values are" "The scale of the content on the pages of the output PDF. Acceptable values are floats.")
+ " floats.")
private float scaleFactor; private float scaleFactor;
} }

View File

@ -22,7 +22,6 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
@Schema( @Schema(
description = "Font size for page numbers", description = "Font size for page numbers",
minimum = "1", minimum = "1",
defaultValue = "12",
requiredMode = RequiredMode.REQUIRED) requiredMode = RequiredMode.REQUIRED)
private float fontSize; private float fontSize;
@ -34,18 +33,15 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
@Schema( @Schema(
description = description =
"Position: 1-9 representing positions on the page (1=top-left, 2=top-center," "Position: 1-9 representing positions on the page (1=top-left, 5=center, 9=bottom-right)",
+ " 3=top-right, 4=middle-left, 5=middle-center, 6=middle-right," minimum = "1",
+ " 7=bottom-left, 8=bottom-center, 9=bottom-right)", maximum = "9",
allowableValues = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
defaultValue = "8",
requiredMode = RequiredMode.REQUIRED) requiredMode = RequiredMode.REQUIRED)
private int position; private int position;
@Schema( @Schema(
description = "Starting number for page numbering", description = "Starting number for page numbering",
minimum = "1", minimum = "1",
defaultValue = "1",
requiredMode = RequiredMode.REQUIRED) requiredMode = RequiredMode.REQUIRED)
private int startingNumber; private int startingNumber;
@ -57,8 +53,7 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
@Schema( @Schema(
description = description =
"Custom text pattern. Available variables: {n}=current page number," "Custom text pattern. Available variables: {n}=current page number, {total}=total pages, {filename}=original filename",
+ " {total}=total pages, {filename}=original filename",
example = "Page {n} of {total}", example = "Page {n} of {total}",
defaultValue = "{n}", defaultValue = "{n}",
requiredMode = RequiredMode.NOT_REQUIRED) requiredMode = RequiredMode.NOT_REQUIRED)

View File

@ -19,69 +19,51 @@ public class AddStampRequest extends PDFWithPageNums {
requiredMode = Schema.RequiredMode.REQUIRED) requiredMode = Schema.RequiredMode.REQUIRED)
private String stampType; private String stampType;
@Schema(description = "The stamp text", defaultValue = "Stirling Software") @Schema(description = "The stamp text")
private String stampText; private String stampText;
@Schema(description = "The stamp image") @Schema(description = "The stamp image")
private MultipartFile stampImage; private MultipartFile stampImage;
@Schema( @Schema(
description = "The selected alphabet of the stamp text", description = "The selected alphabet",
allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"}, allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"},
defaultValue = "roman") defaultValue = "roman")
private String alphabet = "roman"; private String alphabet = "roman";
@Schema( @Schema(description = "The font size of the stamp text", example = "30")
description = "The font size of the stamp text and image", private float fontSize = 30;
defaultValue = "30",
requiredMode = Schema.RequiredMode.REQUIRED)
private float fontSize;
@Schema( @Schema(description = "The rotation of the stamp in degrees", example = "0")
description = "The rotation of the stamp in degrees", private float rotation = 0;
defaultValue = "0",
requiredMode = Schema.RequiredMode.REQUIRED)
private float rotation;
@Schema( @Schema(description = "The opacity of the stamp (0.0 - 1.0)", example = "0.5")
description = "The opacity of the stamp (0.0 - 1.0)",
defaultValue = "0.5",
requiredMode = Schema.RequiredMode.REQUIRED)
private float opacity; private float opacity;
@Schema( @Schema(
description = description =
"Position for stamp placement based on a 1-9 grid (1: bottom-left, 2: bottom-center," "Position for stamp placement based on a 1-9 grid (1: bottom-left, 2: bottom-center, ..., 9: top-right)",
+ " 3: bottom-right, 4: middle-left, 5: middle-center, 6: middle-right," example = "1")
+ " 7: top-left, 8: top-center, 9: top-right)",
allowableValues = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
defaultValue = "5",
requiredMode = Schema.RequiredMode.REQUIRED)
private int position; private int position;
@Schema( @Schema(
description = description =
"Override X coordinate for stamp placement. If set, it will override the" "Override X coordinate for stamp placement. If set, it will override the position-based calculation. Negative value means no override.",
+ " position-based calculation. Negative value means no override.", example = "-1")
defaultValue = "-1", private float overrideX = -1; // Default to -1 indicating no override
requiredMode = Schema.RequiredMode.REQUIRED)
private float overrideX; // Default to -1 indicating no override
@Schema( @Schema(
description = description =
"Override Y coordinate for stamp placement. If set, it will override the" "Override Y coordinate for stamp placement. If set, it will override the position-based calculation. Negative value means no override.",
+ " position-based calculation. Negative value means no override.", example = "-1")
defaultValue = "-1", private float overrideY = -1; // Default to -1 indicating no override
requiredMode = Schema.RequiredMode.REQUIRED)
private float overrideY; // Default to -1 indicating no override
@Schema( @Schema(
description = "Specifies the margin size for the stamp.", description = "Specifies the margin size for the stamp.",
allowableValues = {"small", "medium", "large", "x-large"}, allowableValues = {"small", "medium", "large", "x-large"},
defaultValue = "medium", defaultValue = "medium")
requiredMode = Schema.RequiredMode.REQUIRED) private String customMargin = "medium";
private String customMargin;
@Schema(description = "The color of the stamp text", defaultValue = "#d3d3d3") @Schema(description = "The color for stamp", defaultValue = "#d3d3d3")
private String customColor; private String customColor = "#d3d3d3";
} }

View File

@ -16,5 +16,5 @@ public class AutoSplitPdfRequest extends PDFFile {
"Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.",
requiredMode = Schema.RequiredMode.NOT_REQUIRED, requiredMode = Schema.RequiredMode.NOT_REQUIRED,
defaultValue = "false") defaultValue = "false")
private Boolean duplexMode; private boolean duplexMode;
} }

View File

@ -16,5 +16,5 @@ public class ExtractHeaderRequest extends PDFFile {
"Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.",
requiredMode = Schema.RequiredMode.NOT_REQUIRED, requiredMode = Schema.RequiredMode.NOT_REQUIRED,
defaultValue = "false") defaultValue = "false")
private Boolean useFirstTextAsFallback; private boolean useFirstTextAsFallback;
} }

View File

@ -12,37 +12,36 @@ import lombok.EqualsAndHashCode;
public class ExtractImageScansRequest { public class ExtractImageScansRequest {
@Schema( @Schema(
description = "The input file containing image scans", description = "The input file containing image scans",
requiredMode = Schema.RequiredMode.REQUIRED, requiredMode = Schema.RequiredMode.REQUIRED)
format = "binary")
private MultipartFile fileInput; private MultipartFile fileInput;
@Schema( @Schema(
description = "The angle threshold for the image scan extraction", description = "The angle threshold for the image scan extraction",
requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "5",
defaultValue = "5") example = "5")
private int angleThreshold; private int angleThreshold = 5;
@Schema( @Schema(
description = "The tolerance for the image scan extraction", description = "The tolerance for the image scan extraction",
requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "20",
defaultValue = "20") example = "20")
private int tolerance; private int tolerance = 20;
@Schema( @Schema(
description = "The minimum area for the image scan extraction", description = "The minimum area for the image scan extraction",
requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "8000",
defaultValue = "8000") example = "8000")
private int minArea; private int minArea = 8000;
@Schema( @Schema(
description = "The minimum contour area for the image scan extraction", description = "The minimum contour area for the image scan extraction",
requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "500",
defaultValue = "500") example = "500")
private int minContourArea; private int minContourArea = 500;
@Schema( @Schema(
description = "The border size for the image scan extraction", description = "The border size for the image scan extraction",
requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "1",
defaultValue = "1") example = "1")
private int borderSize; private int borderSize = 1;
} }

View File

@ -13,9 +13,6 @@ public class FlattenRequest extends PDFFile {
@Schema( @Schema(
description = description =
"True to flatten only the forms, false to flatten full PDF (Convert page to" "True to flatten only the forms, false to flatten full PDF (Convert page to image)")
+ " image)",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "false")
private Boolean flattenOnlyForms; private Boolean flattenOnlyForms;
} }

View File

@ -13,72 +13,38 @@ import stirling.software.SPDF.model.api.PDFFile;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class MetadataRequest extends PDFFile { public class MetadataRequest extends PDFFile {
@Schema( @Schema(description = "Delete all metadata if set to true")
description = "Delete all metadata if set to true", private boolean deleteAll;
defaultValue = "false",
requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean deleteAll;
@Schema( @Schema(description = "The author of the document")
description = "The author of the document",
defaultValue = "author",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String author; private String author;
@Schema( @Schema(description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)")
description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)",
pattern = "yyyy/MM/dd HH:mm:ss",
defaultValue = "2023/10/01 12:00:00",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String creationDate; private String creationDate;
@Schema( @Schema(description = "The creator of the document")
description = "The creator of the document",
defaultValue = "creator",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String creator; private String creator;
@Schema( @Schema(description = "The keywords for the document")
description = "The keywords for the document",
defaultValue = "keywords",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String keywords; private String keywords;
@Schema( @Schema(description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)")
description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)",
pattern = "yyyy/MM/dd HH:mm:ss",
defaultValue = "2023/10/01 12:00:00",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String modificationDate; private String modificationDate;
@Schema( @Schema(description = "The producer of the document")
description = "The producer of the document",
defaultValue = "producer",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String producer; private String producer;
@Schema( @Schema(description = "The subject of the document")
description = "The subject of the document",
defaultValue = "subject",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String subject; private String subject;
@Schema( @Schema(description = "The title of the document")
description = "The title of the document",
defaultValue = "title",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String title; private String title;
@Schema( @Schema(description = "The trapped status of the document")
description = "The trapped status of the document",
defaultValue = "False",
allowableValues = {"True", "False", "Unknown"},
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String trapped; private String trapped;
@Schema( @Schema(
description = description =
"Map list of key and value of custom parameters. Note these must start with" "Map list of key and value of custom parameters. Note these must start with customKey and customValue if they are non-standard")
+ " customKey and customValue if they are non-standard")
private Map<String, String> allRequestParams; private Map<String, String> allRequestParams;
} }

View File

@ -13,36 +13,30 @@ public class OptimizePdfRequest extends PDFFile {
@Schema( @Schema(
description = description =
"The level of optimization to apply to the PDF file. Higher values indicate" "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.",
+ " greater compression but may reduce quality.",
defaultValue = "5",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"1", "2", "3", "4", "5", "6", "7", "8", "9"}) allowableValues = {"1", "2", "3", "4", "5", "6", "7", "8", "9"})
private Integer optimizeLevel; private Integer optimizeLevel;
@Schema( @Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.")
description = "The expected output size, e.g. '100MB', '25KB', etc.",
defaultValue = "25KB",
requiredMode = Schema.RequiredMode.REQUIRED)
private String expectedOutputSize; private String expectedOutputSize;
@Schema( @Schema(
description = "Whether to linearize the PDF for faster web viewing. Default is false.", description = "Whether to linearize the PDF for faster web viewing. Default is false.",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "false") defaultValue = "false")
private Boolean linearize = false; private Boolean linearize = false;
@Schema( @Schema(
description = description =
"Whether to normalize the PDF content for better compatibility. Default is" "Whether to normalize the PDF content for better compatibility. Default is false.",
+ " false.",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "false") defaultValue = "false")
private Boolean normalize = false; private Boolean normalize = false;
@Schema( @Schema(
description = "Whether to convert the PDF to grayscale. Default is false.", description = "Whether to convert the PDF to grayscale. Default is false.",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "false") defaultValue = "false")
private Boolean grayscale = false; private Boolean grayscale = false;
public Boolean getGrayscale() {
return grayscale;
}
} }

View File

@ -13,27 +13,21 @@ import stirling.software.SPDF.model.api.PDFFile;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class OverlayImageRequest extends PDFFile { public class OverlayImageRequest extends PDFFile {
@Schema( @Schema(description = "The image file to be overlaid onto the PDF.")
description = "The image file to be overlaid onto the PDF.",
requiredMode = Schema.RequiredMode.REQUIRED,
format = "binary")
private MultipartFile imageFile; private MultipartFile imageFile;
@Schema( @Schema(
description = "The x-coordinate at which to place the top-left corner of the image.", description = "The x-coordinate at which to place the top-left corner of the image.",
requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
defaultValue = "0")
private float x; private float x;
@Schema( @Schema(
description = "The y-coordinate at which to place the top-left corner of the image.", description = "The y-coordinate at which to place the top-left corner of the image.",
requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
defaultValue = "0")
private float y; private float y;
@Schema( @Schema(
description = "Whether to overlay the image onto every page of the PDF.", description = "Whether to overlay the image onto every page of the PDF.",
requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
defaultValue = "false") private boolean everyPage;
private Boolean everyPage;
} }

View File

@ -13,21 +13,16 @@ import stirling.software.SPDF.model.api.PDFFile;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ProcessPdfWithOcrRequest extends PDFFile { public class ProcessPdfWithOcrRequest extends PDFFile {
@Schema( @Schema(description = "List of languages to use in OCR processing")
description = "List of languages to use in OCR processing, e.g., 'eng', 'deu'",
requiredMode = Schema.RequiredMode.REQUIRED,
defaultValue = "[\"eng\"]")
private List<String> languages; private List<String> languages;
@Schema( @Schema(
description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"skip-text", "force-ocr", "Normal"}) allowableValues = {"skip-text", "force-ocr", "Normal"})
private String ocrType; private String ocrType;
@Schema( @Schema(
description = "Specify the OCR render type, either 'hocr' or 'sandwich'", description = "Specify the OCR render type, either 'hocr' or 'sandwich'",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"hocr", "sandwich"}, allowableValues = {"hocr", "sandwich"},
defaultValue = "hocr") defaultValue = "hocr")
private String ocrRenderType = "hocr"; private String ocrRenderType = "hocr";

View File

@ -13,17 +13,13 @@ public class RemoveBlankPagesRequest extends PDFFile {
@Schema( @Schema(
description = "The threshold value to determine blank pages", description = "The threshold value to determine blank pages",
requiredMode = Schema.RequiredMode.REQUIRED, example = "10",
minimum = "0",
maximum = "255",
defaultValue = "10") defaultValue = "10")
private int threshold; private int threshold = 10;
@Schema( @Schema(
description = "The percentage of white color on a page to consider it as blank", description = "The percentage of white color on a page to consider it as blank",
requiredMode = Schema.RequiredMode.REQUIRED, example = "99.9",
minimum = "0.1",
maximum = "100",
defaultValue = "99.9") defaultValue = "99.9")
private float whitePercent; private float whitePercent = 99.9f;
} }

Some files were not shown because too many files have changed in this diff Show More