mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-23 16:05:09 +00:00
Compare commits
155 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
80b5097532 | ||
![]() |
1fc552c2c7 | ||
![]() |
fca1ea2cbe | ||
![]() |
cc3aa6b703 | ||
![]() |
077962872e | ||
![]() |
76808b716a | ||
![]() |
96315ee76f | ||
![]() |
7d7f1272e4 | ||
![]() |
ee8030c1c4 | ||
![]() |
bbaadc1822 | ||
![]() |
9923411ade | ||
![]() |
5a8162ff60 | ||
![]() |
387ae5934d | ||
![]() |
9d5f97c5ad | ||
![]() |
c080158b1c | ||
![]() |
ddad1eddef | ||
![]() |
ec805209a5 | ||
![]() |
50aa5e718d | ||
![]() |
64766a129c | ||
![]() |
cdd1ab704f | ||
![]() |
8632ccb870 | ||
![]() |
a208d55525 | ||
![]() |
552f2ced4d | ||
![]() |
ee41dc11c2 | ||
![]() |
5a272f80b0 | ||
![]() |
2fb13f4f46 | ||
![]() |
45b4588a42 | ||
![]() |
f82662aaaf | ||
![]() |
391bb4545b | ||
![]() |
b3a2bfbe71 | ||
![]() |
03cfad9528 | ||
![]() |
85eb78e707 | ||
![]() |
625900557a | ||
![]() |
d98ebddf49 | ||
![]() |
aaa11fd3e3 | ||
![]() |
ff6353d9ab | ||
![]() |
406695e167 | ||
![]() |
5534f4b64a | ||
![]() |
e74dbf391c | ||
![]() |
3804dd3988 | ||
![]() |
136f16f613 | ||
![]() |
3ddb370f69 | ||
![]() |
fe47cac608 | ||
![]() |
da2473c784 | ||
![]() |
d219198b9b | ||
![]() |
4cb0caaee1 | ||
![]() |
e1fc94929d | ||
![]() |
a2db47d3af | ||
![]() |
0ca23e6835 | ||
![]() |
06db69ed91 | ||
![]() |
c66bf56260 | ||
![]() |
dda3f65f40 | ||
![]() |
9bacebf2e9 | ||
![]() |
f9559151d8 | ||
![]() |
2287d3c08b | ||
![]() |
fbf8f0e419 | ||
![]() |
89580387a2 | ||
![]() |
8fbeeb7161 | ||
![]() |
71ae880a31 | ||
![]() |
23ea86c377 | ||
![]() |
da365c12b4 | ||
![]() |
ffcbf31cca | ||
![]() |
9c83dd270a | ||
![]() |
0b15fa9de0 | ||
![]() |
a49eb3a629 | ||
![]() |
5393ae24cb | ||
![]() |
142dba185c | ||
![]() |
069b71be2c | ||
![]() |
2649b18ab4 | ||
![]() |
3c507eb303 | ||
![]() |
0ee52a4181 | ||
![]() |
d1b677726b | ||
![]() |
0cbe7fe255 | ||
![]() |
493e5daeda | ||
![]() |
bcfe5b7b19 | ||
![]() |
9fc71e851c | ||
![]() |
0b4747e827 | ||
![]() |
1f2365f03c | ||
![]() |
bdc35519da | ||
![]() |
c7d6a063d7 | ||
![]() |
baaaa5a0b2 | ||
![]() |
461f98be3e | ||
![]() |
9fbb0325b5 | ||
![]() |
47ac4a4730 | ||
![]() |
2d5091c8e3 | ||
![]() |
b78d05e8ee | ||
![]() |
4272dfa240 | ||
![]() |
0006a77b5d | ||
![]() |
6cef1fac5f | ||
![]() |
2df70daf16 | ||
![]() |
a0cb56ab0c | ||
![]() |
febe67eac8 | ||
![]() |
41cd7d8f47 | ||
![]() |
5a50947e5e | ||
![]() |
392a10a8c1 | ||
![]() |
275039015d | ||
![]() |
149fe8755d | ||
![]() |
2b9a3924d3 | ||
![]() |
6d8173b021 | ||
![]() |
58937a6e91 | ||
![]() |
3d312c2fd1 | ||
![]() |
6aaedf52ea | ||
![]() |
3591ef5cb7 | ||
![]() |
02799b8aa5 | ||
![]() |
28a259ec95 | ||
![]() |
9cc4f14465 | ||
![]() |
5d9d8a5625 | ||
![]() |
a8c6a8342c | ||
![]() |
9697d1abe1 | ||
![]() |
4883573e87 | ||
![]() |
63fdb958ef | ||
![]() |
55b9286958 | ||
![]() |
3cc3037408 | ||
![]() |
70a9b5f009 | ||
![]() |
92f5c5de7a | ||
![]() |
b08bc191fc | ||
![]() |
d12aca0ca6 | ||
![]() |
5ec78b5425 | ||
![]() |
3bec51f4e5 | ||
![]() |
d83d8b4748 | ||
![]() |
03d27013d6 | ||
![]() |
679ecdd50d | ||
![]() |
631c4fef0b | ||
![]() |
209c76d885 | ||
![]() |
3293d0d8a1 | ||
![]() |
45462dc5d4 | ||
![]() |
c0e93cd5e5 | ||
![]() |
2217cfb95d | ||
![]() |
214404b5c9 | ||
![]() |
ddc2daea52 | ||
![]() |
ff602a20a0 | ||
![]() |
85ac1259af | ||
![]() |
4acfc713e9 | ||
![]() |
e9f46aec53 | ||
![]() |
909c0aed7a | ||
![]() |
c9879c1d28 | ||
![]() |
38e472a631 | ||
![]() |
bedc3d02d7 | ||
![]() |
be1a9cc8da | ||
![]() |
055c642136 | ||
![]() |
61521b5bf3 | ||
![]() |
ccf1b23d67 | ||
![]() |
14f76b6146 | ||
![]() |
6780bb4a30 | ||
![]() |
feb84f001c | ||
![]() |
8c061ea644 | ||
![]() |
ea5515b614 | ||
![]() |
b1a6e1b481 | ||
![]() |
f2f11496a2 | ||
![]() |
75c325d15a | ||
![]() |
adcfe629f2 | ||
![]() |
35304a1491 | ||
![]() |
cc938e1751 | ||
![]() |
b65624cf57 | ||
![]() |
8bfdb2abb5 |
14
.gitattributes
vendored
14
.gitattributes
vendored
@ -1,10 +1,10 @@
|
|||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
|
||||||
# Ignore all JavaScript files in a directory
|
# Ignore all JavaScript files in a directory
|
||||||
src/main/resources/static/pdfjs/* linguist-vendored
|
stirling-pdf/src/main/resources/static/pdfjs/* linguist-vendored
|
||||||
src/main/resources/static/pdfjs/** linguist-vendored
|
stirling-pdf/src/main/resources/static/pdfjs/** linguist-vendored
|
||||||
src/main/resources/static/pdfjs-legacy/* linguist-vendored
|
stirling-pdf/src/main/resources/static/pdfjs-legacy/* linguist-vendored
|
||||||
src/main/resources/static/pdfjs-legacy/** linguist-vendored
|
stirling-pdf/src/main/resources/static/pdfjs-legacy/** linguist-vendored
|
||||||
src/main/resources/static/css/bootstrap-icons.css linguist-vendored
|
stirling-pdf/src/main/resources/static/css/bootstrap-icons.css linguist-vendored
|
||||||
src/main/resources/static/css/bootstrap.min.css linguist-vendored
|
stirling-pdf/src/main/resources/static/css/bootstrap.min.css linguist-vendored
|
||||||
src/main/resources/static/css/fonts/* linguist-vendored
|
stirling-pdf/src/main/resources/static/css/fonts/* linguist-vendored
|
||||||
|
33
.github/actions/setup-bot/action.yml
vendored
Normal file
33
.github/actions/setup-bot/action.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
name: 'Setup GitHub App Bot'
|
||||||
|
description: 'Generates a GitHub App Token and configures Git for a bot'
|
||||||
|
inputs:
|
||||||
|
app-id:
|
||||||
|
description: 'GitHub App ID'
|
||||||
|
required: True
|
||||||
|
private-key:
|
||||||
|
description: 'GitHub App Private Key'
|
||||||
|
required: True
|
||||||
|
outputs:
|
||||||
|
token:
|
||||||
|
description: 'Generated GitHub App Token'
|
||||||
|
value: ${{ steps.generate-token.outputs.token }}
|
||||||
|
committer:
|
||||||
|
description: 'Committer string for Git'
|
||||||
|
value: "${{ steps.generate-token.outputs.app-slug }}[bot] <${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
|
||||||
|
app-slug:
|
||||||
|
description: 'GitHub App slug'
|
||||||
|
value: ${{ steps.generate-token.outputs.app-slug }}
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- name: Generate a GitHub App Token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
||||||
|
with:
|
||||||
|
app-id: ${{ inputs.app-id }}
|
||||||
|
private-key: ${{ inputs.private-key }}
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "${{ steps.generate-token.outputs.app-slug }}[bot]"
|
||||||
|
git config --global user.email "${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com"
|
||||||
|
shell: bash
|
139
.github/labeler-config-srvaroa.yml
vendored
Normal file
139
.github/labeler-config-srvaroa.yml
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
version: 1
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- label: "Bugfix"
|
||||||
|
title: '^fix:.*'
|
||||||
|
|
||||||
|
- label: "enhancement"
|
||||||
|
title: '^feat:.*'
|
||||||
|
|
||||||
|
- label: "build"
|
||||||
|
title: '^build:.*'
|
||||||
|
|
||||||
|
- label: "chore"
|
||||||
|
title: '^chore:.*'
|
||||||
|
|
||||||
|
- label: "ci"
|
||||||
|
title: '^ci:.*'
|
||||||
|
|
||||||
|
- label: "perf"
|
||||||
|
title: '^perf:.*'
|
||||||
|
|
||||||
|
- label: "refactor"
|
||||||
|
title: '^refactor:.*'
|
||||||
|
|
||||||
|
- label: "revert"
|
||||||
|
title: '^revert:.*'
|
||||||
|
|
||||||
|
- label: "style"
|
||||||
|
title: '^style:.*'
|
||||||
|
|
||||||
|
- label: "Documentation"
|
||||||
|
title: '^docs:.*'
|
||||||
|
|
||||||
|
- label: 'API'
|
||||||
|
title: '.*openapi.*'
|
||||||
|
|
||||||
|
- label: 'Translation'
|
||||||
|
files:
|
||||||
|
- 'stirling-pdf/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}.properties'
|
||||||
|
- 'scripts/ignore_translation.toml'
|
||||||
|
- 'stirling-pdf/src/main/resources/templates/fragments/languages.html'
|
||||||
|
- '.github/scripts/check_language_properties.py'
|
||||||
|
|
||||||
|
- label: 'Front End'
|
||||||
|
files:
|
||||||
|
- 'stirling-pdf/src/main/resources/templates/.*'
|
||||||
|
- 'proprietary/src/main/resources/templates/.*'
|
||||||
|
- 'stirling-pdf/src/main/resources/static/.*'
|
||||||
|
- 'proprietary/src/main/resources/static/.*'
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/.*'
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/UI/.*'
|
||||||
|
|
||||||
|
- label: 'Java'
|
||||||
|
files:
|
||||||
|
- 'common/src/main/java/.*.java'
|
||||||
|
- 'proprietary/src/main/java/.*.java'
|
||||||
|
- 'stirling-pdf/src/main/java/.*.java'
|
||||||
|
|
||||||
|
- label: 'Back End'
|
||||||
|
files:
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/config/.*'
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/.*'
|
||||||
|
- 'stirling-pdf/src/main/resources/settings.yml.template'
|
||||||
|
- 'stirling-pdf/src/main/resources/application.properties'
|
||||||
|
- 'stirling-pdf/src/main/resources/banner.txt'
|
||||||
|
- 'scripts/png_to_webp.py'
|
||||||
|
- 'split_photos.py'
|
||||||
|
- 'application.properties'
|
||||||
|
|
||||||
|
- label: 'Security'
|
||||||
|
files:
|
||||||
|
- 'proprietary/src/main/java/stirling/software/proprietary/security/.*'
|
||||||
|
- 'scripts/download-security-jar.sh'
|
||||||
|
- '.github/workflows/dependency-review.yml'
|
||||||
|
- '.github/workflows/scorecards.yml'
|
||||||
|
|
||||||
|
- label: 'API'
|
||||||
|
files:
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/.*'
|
||||||
|
- 'stirling-pdf/src/main/java/stirling/software/SPDF/model/api/.*'
|
||||||
|
- 'scripts/png_to_webp.py'
|
||||||
|
- 'split_photos.py'
|
||||||
|
- '.github/workflows/swagger.yml'
|
||||||
|
|
||||||
|
- label: 'Documentation'
|
||||||
|
files:
|
||||||
|
- '.*.md'
|
||||||
|
- 'scripts/counter_translation.py'
|
||||||
|
- 'scripts/ignore_translation.toml'
|
||||||
|
|
||||||
|
- label: 'Docker'
|
||||||
|
files:
|
||||||
|
- '.github/workflows/build.yml'
|
||||||
|
- '.github/workflows/push-docker.yml'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile.fat'
|
||||||
|
- 'Dockerfile.ultra-lite'
|
||||||
|
- 'exampleYmlFiles/*.yml'
|
||||||
|
- 'scripts/download-security-jar.sh'
|
||||||
|
- 'scripts/init.sh'
|
||||||
|
- 'scripts/init-without-ocr.sh'
|
||||||
|
- 'scripts/installFonts.sh'
|
||||||
|
- 'test.sh'
|
||||||
|
- 'test2.sh'
|
||||||
|
|
||||||
|
- label: 'Devtools'
|
||||||
|
files:
|
||||||
|
- '.devcontainer/.*'
|
||||||
|
- 'Dockerfile.dev'
|
||||||
|
- '.vscode/.*'
|
||||||
|
- '.editorconfig'
|
||||||
|
- '.pre-commit-config'
|
||||||
|
- '.github/workflows/pre_commit.yml'
|
||||||
|
- 'HowToAddNewLanguage.md'
|
||||||
|
|
||||||
|
- label: 'Test'
|
||||||
|
files:
|
||||||
|
- 'common/src/test/.*'
|
||||||
|
- 'proprietary/src/test/.*'
|
||||||
|
- 'stirling-pdf/src/test/.*'
|
||||||
|
- 'testing/.*'
|
||||||
|
- '.github/workflows/scorecards.yml'
|
||||||
|
|
||||||
|
- label: 'Github'
|
||||||
|
files:
|
||||||
|
- '.github/.*'
|
||||||
|
|
||||||
|
- label: 'Gradle'
|
||||||
|
files:
|
||||||
|
- 'gradle/.*'
|
||||||
|
- 'gradlew'
|
||||||
|
- 'gradlew.bat'
|
||||||
|
- 'settings.gradle'
|
||||||
|
- 'build.gradle'
|
||||||
|
- 'common/build.gradle'
|
||||||
|
- 'proprietary/build.gradle'
|
||||||
|
- 'stirling-pdf/build.gradle'
|
57
.github/labeler-config.yml
vendored
57
.github/labeler-config.yml
vendored
@ -1,60 +1,45 @@
|
|||||||
Translation:
|
Translation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/resources/messages_*_*.properties'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/messages_*_*.properties'
|
||||||
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
|
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
|
||||||
- any-glob-to-any-file: 'src/main/resources/templates/fragments/languages.html'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/fragments/languages.html'
|
||||||
|
|
||||||
Front End:
|
Front End:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/resources/templates/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/resources/static/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/static/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/**'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/**'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/UI/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/UI/**/*'
|
||||||
|
|
||||||
Java:
|
Java:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/java/**/*.java'
|
- any-glob-to-any-file: 'common/src/main/java/**/*.java'
|
||||||
|
- any-glob-to-any-file: 'proprietary/src/main/java/**/*.java'
|
||||||
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/**/*.java'
|
||||||
|
|
||||||
Back End:
|
Back End:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/config/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/resources/settings.yml.template'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/settings.yml.template'
|
||||||
- any-glob-to-any-file: 'src/main/resources/application.properties'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/application.properties'
|
||||||
- any-glob-to-any-file: 'src/main/resources/banner.txt'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/banner.txt'
|
||||||
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
||||||
- any-glob-to-any-file: 'split_photos.py'
|
- any-glob-to-any-file: 'split_photos.py'
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java'
|
- any-glob-to-any-file: 'proprietary/src/main/java/stirling/software/proprietary/security/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/EmailController.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/H2SQLController.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/UserController.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/api/Email.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/exception/BackupNotFoundException.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/exception/NoProviderFoundExceptionjava'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AuthenticationType.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AttemptCounter.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/Authority.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/PersistentLogin.java'
|
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/SessionEntity.java'
|
|
||||||
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
||||||
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
|
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
|
||||||
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
|
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
|
||||||
|
|
||||||
API:
|
API:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/api/**/*'
|
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/model/api/**/*'
|
||||||
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
||||||
- any-glob-to-any-file: 'split_photos.py'
|
- any-glob-to-any-file: 'split_photos.py'
|
||||||
- any-glob-to-any-file: '.github/workflows/swagger.yml'
|
- any-glob-to-any-file: '.github/workflows/swagger.yml'
|
||||||
@ -88,7 +73,9 @@ Devtools:
|
|||||||
Test:
|
Test:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'cucumber/**/*'
|
- any-glob-to-any-file: 'cucumber/**/*'
|
||||||
- any-glob-to-any-file: 'src/test/**/*'
|
- any-glob-to-any-file: 'common/src/test/**/*'
|
||||||
|
- any-glob-to-any-file: 'proprietary/src/test/**/*'
|
||||||
|
- any-glob-to-any-file: 'stirling-pdf/src/test/**/*'
|
||||||
- any-glob-to-any-file: 'src/testing/**/*'
|
- any-glob-to-any-file: 'src/testing/**/*'
|
||||||
- any-glob-to-any-file: '.pre-commit-config'
|
- any-glob-to-any-file: '.pre-commit-config'
|
||||||
- any-glob-to-any-file: '.github/workflows/pre_commit.yml'
|
- any-glob-to-any-file: '.github/workflows/pre_commit.yml'
|
||||||
|
64
.github/labels.yml
vendored
64
.github/labels.yml
vendored
@ -111,3 +111,67 @@
|
|||||||
- name: "Devtools"
|
- name: "Devtools"
|
||||||
color: "FF9E1F"
|
color: "FF9E1F"
|
||||||
description: "Development tools"
|
description: "Development tools"
|
||||||
|
- name: "Bugfix"
|
||||||
|
color: "FF9E1F"
|
||||||
|
description: "Pull requests that fix bugs"
|
||||||
|
- name: "Gradle"
|
||||||
|
color: "FF9E1F"
|
||||||
|
description: "Pull requests that update Gradle code"
|
||||||
|
- name: "build"
|
||||||
|
color: "1E90FF"
|
||||||
|
description: "Changes that affect the build system or external dependencies"
|
||||||
|
- name: "chore"
|
||||||
|
color: "FFD700"
|
||||||
|
description: "Routine tasks or maintenance that don't modify src or test files"
|
||||||
|
- name: "ci"
|
||||||
|
color: "4682B4"
|
||||||
|
description: "Changes to CI configuration files and scripts"
|
||||||
|
- name: "perf"
|
||||||
|
color: "FF69B4"
|
||||||
|
description: "Changes that improve performance"
|
||||||
|
- name: "refactor"
|
||||||
|
color: "9932CC"
|
||||||
|
description: "Code changes that neither fix a bug nor add a feature"
|
||||||
|
- name: "revert"
|
||||||
|
color: "DC143C"
|
||||||
|
description: "Reverts a previous commit"
|
||||||
|
- name: "style"
|
||||||
|
color: "FFA500"
|
||||||
|
description: "Changes that do not affect the meaning of the code (formatting, etc.)"
|
||||||
|
- name: "admin"
|
||||||
|
color: "195055"
|
||||||
|
- name: "codex"
|
||||||
|
color: "ededed"
|
||||||
|
description: null
|
||||||
|
- name: "Github"
|
||||||
|
color: "0052CC"
|
||||||
|
- name: "github_actions"
|
||||||
|
color: "000000"
|
||||||
|
description: "Pull requests that update GitHub Actions code"
|
||||||
|
- name: "needs-changes"
|
||||||
|
color: "A65A86"
|
||||||
|
- name: "on-hold"
|
||||||
|
color: "2526F9"
|
||||||
|
- name: "python"
|
||||||
|
color: "2b67c6"
|
||||||
|
description: "Pull requests that update Python code"
|
||||||
|
- name: "size:L"
|
||||||
|
color: "eb9500"
|
||||||
|
description: "This PR changes 100-499 lines ignoring generated files."
|
||||||
|
- name: "size:M"
|
||||||
|
color: "ebb800"
|
||||||
|
description: "This PR changes 30-99 lines ignoring generated files."
|
||||||
|
- name: "size:S"
|
||||||
|
color: "77b800"
|
||||||
|
description: "This PR changes 10-29 lines ignoring generated files."
|
||||||
|
- name: "size:XL"
|
||||||
|
color: "ff823f"
|
||||||
|
description: "This PR changes 500-999 lines ignoring generated files."
|
||||||
|
- name: "size:XS"
|
||||||
|
color: "00ff00"
|
||||||
|
description: "This PR changes 0-9 lines ignoring generated files."
|
||||||
|
- name: "size:XXL"
|
||||||
|
color: "ffb8b8"
|
||||||
|
description: "This PR changes 1000+ lines ignoring generated files."
|
||||||
|
- name: "to research"
|
||||||
|
color: "FBCA04"
|
||||||
|
26
.github/scripts/check_language_properties.py
vendored
26
.github/scripts/check_language_properties.py
vendored
@ -196,7 +196,9 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
|
|
||||||
if len(file_list) == 1:
|
if len(file_list) == 1:
|
||||||
file_arr = file_list[0].split()
|
file_arr = file_list[0].split()
|
||||||
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
|
base_dir = os.path.abspath(
|
||||||
|
os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources")
|
||||||
|
)
|
||||||
|
|
||||||
for file_path in file_arr:
|
for file_path in file_arr:
|
||||||
file_normpath = os.path.normpath(file_path)
|
file_normpath = os.path.normpath(file_path)
|
||||||
@ -216,10 +218,19 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
or (
|
or (
|
||||||
# only local windows command
|
# only local windows command
|
||||||
not file_normpath.startswith(
|
not file_normpath.startswith(
|
||||||
os.path.join("", "src", "main", "resources", "messages_")
|
os.path.join(
|
||||||
|
"", "stirling-pdf", "src", "main", "resources", "messages_"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
and not file_normpath.startswith(
|
and not file_normpath.startswith(
|
||||||
os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
|
os.path.join(
|
||||||
|
os.getcwd(),
|
||||||
|
"stirling-pdf",
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"resources",
|
||||||
|
"messages_",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
or not file_normpath.endswith(".properties")
|
or not file_normpath.endswith(".properties")
|
||||||
@ -317,7 +328,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
report.append("## ❌ Overall Check Status: **_Failed_**")
|
report.append("## ❌ Overall Check Status: **_Failed_**")
|
||||||
report.append("")
|
report.append("")
|
||||||
report.append(
|
report.append(
|
||||||
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)"
|
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/messages_en_GB.properties)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
report.append("## ✅ Overall Check Status: **_Success_**")
|
report.append("## ✅ Overall Check Status: **_Success_**")
|
||||||
@ -377,7 +388,12 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
file_list = glob.glob(
|
file_list = glob.glob(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
os.getcwd(), "src", "main", "resources", "messages_*.properties"
|
os.getcwd(),
|
||||||
|
"stirling-pdf",
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"resources",
|
||||||
|
"messages_*.properties",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
update_missing_keys(args.reference_file, file_list)
|
update_missing_keys(args.reference_file, file_list)
|
||||||
|
68
.github/workflows/PR-Demo-Comment-with-react.yml
vendored
68
.github/workflows/PR-Demo-Comment-with-react.yml
vendored
@ -37,11 +37,12 @@ jobs:
|
|||||||
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
|
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
|
||||||
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
|
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
|
||||||
comment_id: ${{ github.event.comment.id }}
|
comment_id: ${{ github.event.comment.id }}
|
||||||
enable_security: ${{ steps.check-security-flag.outputs.enable_security }}
|
disable_security: ${{ steps.check-security-flag.outputs.disable_security }}
|
||||||
|
enable_pro: ${{ steps.check-pro-flag.outputs.enable_pro }}
|
||||||
|
enable_enterprise: ${{ steps.check-pro-flag.outputs.enable_enterprise }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -92,10 +93,29 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if [[ "$COMMENT_BODY" == *"security"* ]] || [[ "$COMMENT_BODY" == *"login"* ]]; then
|
if [[ "$COMMENT_BODY" == *"security"* ]] || [[ "$COMMENT_BODY" == *"login"* ]]; then
|
||||||
echo "Security flags detected in comment"
|
echo "Security flags detected in comment"
|
||||||
echo "enable_security=true" >> $GITHUB_OUTPUT
|
echo "disable_security=false" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "No security flags detected in comment"
|
echo "No security flags detected in comment"
|
||||||
echo "enable_security=false" >> $GITHUB_OUTPUT
|
echo "disable_security=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check for pro flag
|
||||||
|
id: check-pro-flag
|
||||||
|
env:
|
||||||
|
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||||
|
run: |
|
||||||
|
if [[ "$COMMENT_BODY" == *"pro"* ]] || [[ "$COMMENT_BODY" == *"premium"* ]]; then
|
||||||
|
echo "pro flags detected in comment"
|
||||||
|
echo "enable_pro=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "enable_enterprise=false" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "$COMMENT_BODY" == *"enterprise"* ]]; then
|
||||||
|
echo "enterprise flags detected in comment"
|
||||||
|
echo "enable_enterprise=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "enable_pro=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "No pro or enterprise flags detected in comment"
|
||||||
|
echo "enable_pro=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "enable_enterprise=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Add 'in_progress' reaction to comment
|
- name: Add 'in_progress' reaction to comment
|
||||||
@ -129,7 +149,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -155,17 +175,17 @@ jobs:
|
|||||||
|
|
||||||
- name: Run Gradle Command
|
- name: Run Gradle Command
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then
|
if [ "${{ needs.check-comment.outputs.disable_security }}" == "true" ]; then
|
||||||
export DOCKER_ENABLE_SECURITY=true
|
export DISABLE_ADDITIONAL_FEATURES=true
|
||||||
else
|
else
|
||||||
export DOCKER_ENABLE_SECURITY=false
|
export DISABLE_ADDITIONAL_FEATURES=false
|
||||||
fi
|
fi
|
||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
env:
|
env:
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
@ -180,7 +200,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@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
@ -199,16 +219,31 @@ jobs:
|
|||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
# Set security settings based on flags
|
# Set security settings based on flags
|
||||||
if [ "${{ needs.check-comment.outputs.enable_security }}" == "true" ]; then
|
if [ "${{ needs.check-comment.outputs.disable_security }}" == "false" ]; then
|
||||||
DOCKER_SECURITY="true"
|
DISABLE_ADDITIONAL_FEATURES="false"
|
||||||
LOGIN_SECURITY="true"
|
LOGIN_SECURITY="true"
|
||||||
SECURITY_STATUS="🔒 Security Enabled"
|
SECURITY_STATUS="🔒 Security Enabled"
|
||||||
else
|
else
|
||||||
DOCKER_SECURITY="false"
|
DISABLE_ADDITIONAL_FEATURES="true"
|
||||||
LOGIN_SECURITY="false"
|
LOGIN_SECURITY="false"
|
||||||
SECURITY_STATUS="Security Disabled"
|
SECURITY_STATUS="Security Disabled"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Set pro/enterprise settings (enterprise implies pro)
|
||||||
|
if [ "${{ needs.check-comment.outputs.enable_enterprise }}" == "true" ]; then
|
||||||
|
PREMIUM_ENABLED="true"
|
||||||
|
PREMIUM_KEY="${{ secrets.ENTERPRISE_KEY }}"
|
||||||
|
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
|
||||||
|
elif [ "${{ needs.check-comment.outputs.enable_pro }}" == "true" ]; then
|
||||||
|
PREMIUM_ENABLED="true"
|
||||||
|
PREMIUM_KEY="${{ secrets.PREMIUM_KEY }}"
|
||||||
|
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
|
||||||
|
else
|
||||||
|
PREMIUM_ENABLED="false"
|
||||||
|
PREMIUM_KEY=""
|
||||||
|
PREMIUM_PROFEATURES_AUDIT_ENABLED="false"
|
||||||
|
fi
|
||||||
|
|
||||||
# First create the docker-compose content locally
|
# First create the docker-compose content locally
|
||||||
cat > docker-compose.yml << EOF
|
cat > docker-compose.yml << EOF
|
||||||
version: '3.3'
|
version: '3.3'
|
||||||
@ -223,7 +258,7 @@ jobs:
|
|||||||
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
|
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
|
||||||
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
|
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "${DOCKER_SECURITY}"
|
DISABLE_ADDITIONAL_FEATURES: "${DISABLE_ADDITIONAL_FEATURES}"
|
||||||
SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}"
|
SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}"
|
||||||
SYSTEM_DEFAULTLOCALE: en-GB
|
SYSTEM_DEFAULTLOCALE: en-GB
|
||||||
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
|
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
|
||||||
@ -232,6 +267,9 @@ jobs:
|
|||||||
SYSTEM_MAXFILESIZE: "100"
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
METRICS_ENABLED: "true"
|
METRICS_ENABLED: "true"
|
||||||
SYSTEM_GOOGLEVISIBILITY: "false"
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||||
|
PREMIUM_KEY: "${PREMIUM_KEY}"
|
||||||
|
PREMIUM_ENABLED: "${PREMIUM_ENABLED}"
|
||||||
|
PREMIUM_PROFEATURES_AUDIT_ENABLED: "${PREMIUM_PROFEATURES_AUDIT_ENABLED}"
|
||||||
restart: on-failure:5
|
restart: on-failure:5
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
2
.github/workflows/PR-Demo-cleanup.yml
vendored
2
.github/workflows/PR-Demo-cleanup.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
2
.github/workflows/auto-labeler.yml
vendored
2
.github/workflows/auto-labeler.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
35
.github/workflows/auto-labelerV2.yml
vendored
Normal file
35
.github/workflows/auto-labelerV2.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: "Auto Pull Request Labeler V2"
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- uses: srvaroa/labeler@0a20eccb8c94a1ee0bed5f16859aece1c45c3e55 # v1.13.0
|
||||||
|
with:
|
||||||
|
config_path: .github/labeler-config-srvaroa.yml
|
||||||
|
use_local_config: false
|
||||||
|
fail_on_error: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: "${{ steps.setup-bot.outputs.token }}"
|
56
.github/workflows/build.yml
vendored
56
.github/workflows/build.yml
vendored
@ -21,10 +21,11 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
jdk-version: [17, 21]
|
jdk-version: [17, 21]
|
||||||
|
spring-security: [true, false]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -37,32 +38,59 @@ jobs:
|
|||||||
java-version: ${{ matrix.jdk-version }}
|
java-version: ${{ matrix.jdk-version }}
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: Build with Gradle and no spring security
|
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
|
||||||
|
|
||||||
- name: Build with Gradle and with spring security
|
- name: Check Test Reports Exist
|
||||||
run: ./gradlew clean build
|
id: check-reports
|
||||||
env:
|
if: always()
|
||||||
DOCKER_ENABLE_SECURITY: true
|
run: |
|
||||||
|
declare -a dirs=(
|
||||||
|
"stirling-pdf/build/reports/tests/"
|
||||||
|
"stirling-pdf/build/test-results/"
|
||||||
|
"common/build/reports/tests/"
|
||||||
|
"common/build/test-results/"
|
||||||
|
"proprietary/build/reports/tests/"
|
||||||
|
"proprietary/build/test-results/"
|
||||||
|
)
|
||||||
|
missing_reports=()
|
||||||
|
for dir in "${dirs[@]}"; do
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
missing_reports+=("$dir")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ ${#missing_reports[@]} -gt 0 ]; then
|
||||||
|
echo "ERROR: The following required test report directories are missing:"
|
||||||
|
printf '%s\n' "${missing_reports[@]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All required test report directories are present"
|
||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
||||||
path: |
|
path: |
|
||||||
build/reports/tests/
|
stirling-pdf/build/reports/tests/
|
||||||
build/test-results/
|
stirling-pdf/build/test-results/
|
||||||
build/reports/problems/
|
stirling-pdf/build/reports/problems/
|
||||||
|
common/build/reports/tests/
|
||||||
|
common/build/test-results/
|
||||||
|
common/build/reports/problems/
|
||||||
|
proprietary/build/reports/tests/
|
||||||
|
proprietary/build/test-results/
|
||||||
|
proprietary/build/reports/problems/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
check-licence:
|
check-licence:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -106,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -120,7 +148,7 @@ jobs:
|
|||||||
distribution: "adopt"
|
distribution: "adopt"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Install Docker Compose
|
- name: Install Docker Compose
|
||||||
run: |
|
run: |
|
||||||
|
49
.github/workflows/check_properties.yml
vendored
49
.github/workflows/check_properties.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
paths:
|
paths:
|
||||||
- "src/main/resources/messages_*.properties"
|
- "stirling-pdf/src/main/resources/messages_*.properties"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # Allow read access to repository content
|
contents: read # Allow read access to repository content
|
||||||
@ -15,25 +15,28 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
issues: write # Allow posting comments on issues/PRs
|
issues: write # Allow posting comments on issues/PRs
|
||||||
pull-requests: write
|
pull-requests: write # Allow writing to pull requests
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout main branch first
|
- name: Checkout main branch first
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Setup GitHub App Bot
|
||||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Get PR data
|
- name: Get PR data
|
||||||
id: get-pr-data
|
id: get-pr-data
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const prNumber = context.payload.pull_request.number;
|
const prNumber = context.payload.pull_request.number;
|
||||||
const repoOwner = context.payload.repository.owner.login;
|
const repoOwner = context.payload.repository.owner.login;
|
||||||
@ -54,16 +57,30 @@ jobs:
|
|||||||
- name: Fetch PR changed files
|
- name: Fetch PR changed files
|
||||||
id: fetch-pr-changes
|
id: fetch-pr-changes
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
echo "Fetching PR changed files..."
|
echo "Fetching PR changed files..."
|
||||||
echo "Getting list of changed files from PR..."
|
echo "Getting list of changed files from PR..."
|
||||||
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt # Filter only matching property files
|
# Check if PR number exists
|
||||||
|
if [ -z "${{ steps.get-pr-data.outputs.pr_number }}" ]; then
|
||||||
|
echo "Error: PR number is empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Get changed files and filter for properties files, handle case where no matches are found
|
||||||
|
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^stirling-pdf/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt || echo "No matching properties files found in PR"
|
||||||
|
# Check if any files were found
|
||||||
|
if [ ! -s changed_files.txt ]; then
|
||||||
|
echo "No properties files changed in this PR"
|
||||||
|
echo "Workflow will exit early as no relevant files to check"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Found $(wc -l < changed_files.txt) matching properties files"
|
||||||
|
|
||||||
- name: Determine reference file test
|
- name: Determine reference file test
|
||||||
id: determine-file
|
id: determine-file
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
@ -98,8 +115,11 @@ jobs:
|
|||||||
|
|
||||||
// Filter for relevant files based on the PR changes
|
// Filter for relevant files based on the PR changes
|
||||||
const changedFiles = files
|
const changedFiles = files
|
||||||
.map(file => file.filename)
|
.filter(file =>
|
||||||
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file));
|
file.status !== "removed" &&
|
||||||
|
/^stirling-pdf\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename)
|
||||||
|
)
|
||||||
|
.map(file => file.filename);
|
||||||
|
|
||||||
console.log("Changed files:", changedFiles);
|
console.log("Changed files:", changedFiles);
|
||||||
|
|
||||||
@ -137,12 +157,12 @@ jobs:
|
|||||||
|
|
||||||
// Determine reference file
|
// Determine reference file
|
||||||
let referenceFilePath;
|
let referenceFilePath;
|
||||||
if (changedFiles.includes("src/main/resources/messages_en_GB.properties")) {
|
if (changedFiles.includes("stirling-pdf/src/main/resources/messages_en_GB.properties")) {
|
||||||
console.log("Using PR branch reference file.");
|
console.log("Using PR branch reference file.");
|
||||||
const { data: fileContent } = await github.rest.repos.getContent({
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
owner: prRepoOwner,
|
owner: prRepoOwner,
|
||||||
repo: prRepoName,
|
repo: prRepoName,
|
||||||
path: "src/main/resources/messages_en_GB.properties",
|
path: "stirling-pdf/src/main/resources/messages_en_GB.properties",
|
||||||
ref: branch,
|
ref: branch,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,7 +174,7 @@ jobs:
|
|||||||
const { data: fileContent } = await github.rest.repos.getContent({
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
owner: repoOwner,
|
owner: repoOwner,
|
||||||
repo: repoName,
|
repo: repoName,
|
||||||
path: "src/main/resources/messages_en_GB.properties",
|
path: "stirling-pdf/src/main/resources/messages_en_GB.properties",
|
||||||
ref: "main",
|
ref: "main",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -204,6 +224,7 @@ jobs:
|
|||||||
if: env.SCRIPT_OUTPUT != ''
|
if: env.SCRIPT_OUTPUT != ''
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
|
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
|
||||||
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
||||||
@ -219,7 +240,7 @@ jobs:
|
|||||||
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
|
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
|
||||||
|
|
||||||
// Only update or create comments by the action user
|
// Only update or create comments by the action user
|
||||||
const expectedActor = "github-actions[bot]";
|
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]";
|
||||||
|
|
||||||
if (comment && comment.user.login === expectedActor) {
|
if (comment && comment.user.login === expectedActor) {
|
||||||
// Update existing comment
|
// Update existing comment
|
||||||
|
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
52
.github/workflows/licenses-update.yml
vendored
52
.github/workflows/licenses-update.yml
vendored
@ -16,54 +16,52 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
repository-projects: write # Required for enabling automerge
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Check out code
|
||||||
id: generate-token
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "adopt"
|
distribution: "adopt"
|
||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
|
|
||||||
- name: check the licenses for compatibility
|
- name: Check licenses for compatibility
|
||||||
run: ./gradlew clean checkLicense
|
run: ./gradlew clean checkLicense
|
||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
- name: Upload artifact on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: dependencies-without-allowed-license.json
|
name: dependencies-without-allowed-license.json
|
||||||
path: |
|
path: build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
- name: Move and Rename License File
|
- name: Move and rename license file
|
||||||
run: |
|
run: |
|
||||||
mv build/reports/dependency-license/index.json src/main/resources/static/3rdPartyLicenses.json
|
mv build/reports/dependency-license/index.json stirling-pdf/src/main/resources/static/3rdPartyLicenses.json
|
||||||
|
|
||||||
- name: Set up git config
|
- name: Commit changes
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "stirlingbot[bot]"
|
git add stirling-pdf/src/main/resources/static/3rdPartyLicenses.json
|
||||||
git config --global user.email "1113334+stirlingbot[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Run git add
|
|
||||||
run: |
|
|
||||||
git add src/main/resources/static/3rdPartyLicenses.json
|
|
||||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
@ -71,16 +69,16 @@ jobs:
|
|||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.setup-bot.outputs.token }}
|
||||||
commit-message: "Update 3rd Party Licenses"
|
commit-message: "Update 3rd Party Licenses"
|
||||||
committer: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
|
committer: ${{ steps.setup-bot.outputs.committer }}
|
||||||
author: "stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com>"
|
author: ${{ steps.setup-bot.outputs.committer }}
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: update-3rd-party-licenses
|
branch: update-3rd-party-licenses
|
||||||
title: "Update 3rd Party Licenses"
|
title: "Update 3rd Party Licenses"
|
||||||
body: |
|
body: |
|
||||||
Auto-generated by StirlingBot
|
Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot]
|
||||||
labels: licenses,github-actions
|
labels: Licenses,github-actions
|
||||||
draft: false
|
draft: false
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
sign-commits: true
|
sign-commits: true
|
||||||
@ -89,4 +87,4 @@ jobs:
|
|||||||
if: steps.cpr.outputs.pull-request-operation == 'created'
|
if: steps.cpr.outputs.pull-request-operation == 'created'
|
||||||
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
2
.github/workflows/manage-label.yml
vendored
2
.github/workflows/manage-label.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
38
.github/workflows/multiOSReleases.yml
vendored
38
.github/workflows/multiOSReleases.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -48,15 +48,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
enable_security: [true, false]
|
disable_security: [true, false]
|
||||||
include:
|
include:
|
||||||
- enable_security: true
|
- disable_security: false
|
||||||
file_suffix: "-with-login"
|
file_suffix: "-with-login"
|
||||||
- enable_security: false
|
- disable_security: true
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -68,14 +68,14 @@ 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@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.14
|
gradle-version: 8.14
|
||||||
|
|
||||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
- name: Generate jar (Disable Security=${{ matrix.disable_security }})
|
||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.disable_security }}
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Rename binaries
|
- name: Rename binaries
|
||||||
@ -98,15 +98,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
enable_security: [true, false]
|
disable_security: [true, false]
|
||||||
include:
|
include:
|
||||||
- enable_security: true
|
- disable_security: false
|
||||||
file_suffix: "with-login-"
|
file_suffix: "with-login-"
|
||||||
- enable_security: false
|
- disable_security: true
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -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@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.14
|
gradle-version: 8.14
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ jobs:
|
|||||||
- name: Build Installer
|
- name: Build Installer
|
||||||
run: ./gradlew build jpackage -x test --info
|
run: ./gradlew build jpackage -x test --info
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DISABLE_ADDITIONAL_FEATURES: true
|
||||||
STIRLING_PDF_DESKTOP_UI: true
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
BROWSER_OPEN: true
|
BROWSER_OPEN: true
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -248,7 +248,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@ -297,7 +297,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -306,7 +306,7 @@ jobs:
|
|||||||
- name: Display structure of downloaded files
|
- name: Display structure of downloaded files
|
||||||
run: ls -R
|
run: ls -R
|
||||||
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ needs.read_versions.outputs.version }}
|
tag_name: v${{ needs.read_versions.outputs.version }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
45
.github/workflows/pre_commit.yml
vendored
45
.github/workflows/pre_commit.yml
vendored
@ -16,62 +16,53 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Get GitHub App User ID
|
|
||||||
id: get-user-id
|
|
||||||
run: echo "user-id=$(gh api "/users/${{ steps.generate-token.outputs.app-slug }}[bot]" --jq .id)" >> $GITHUB_OUTPUT
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
|
||||||
|
|
||||||
- id: committer
|
|
||||||
run: |
|
|
||||||
echo "string=${{ steps.generate-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
cache: 'pip' # caching pip dependencies
|
cache: 'pip' # caching pip dependencies
|
||||||
|
|
||||||
- name: Run Pre-Commit Hooks
|
- name: Run Pre-Commit Hooks
|
||||||
run: |
|
run: |
|
||||||
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
|
pip install --require-hashes -r ./.github/scripts/requirements_pre_commit.txt
|
||||||
|
|
||||||
- run: pre-commit run --all-files -c .pre-commit-config.yaml
|
- run: pre-commit run --all-files -c .pre-commit-config.yaml
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Set up git config
|
|
||||||
run: |
|
|
||||||
git config --global user.name ${{ steps.generate-token.outputs.app-slug }}[bot]
|
|
||||||
git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com"
|
|
||||||
- name: git add
|
- name: git add
|
||||||
run: |
|
run: |
|
||||||
git add .
|
git add .
|
||||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: env.CHANGES_DETECTED == 'true'
|
if: env.CHANGES_DETECTED == 'true'
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.setup-bot.outputs.token }}
|
||||||
commit-message: ":file_folder: pre-commit"
|
commit-message: ":file_folder: pre-commit"
|
||||||
committer: ${{ steps.committer.outputs.string }}
|
committer: ${{ steps.setup-bot.outputs.committer }}
|
||||||
author: ${{ steps.committer.outputs.string }}
|
author: ${{ steps.setup-bot.outputs.committer }}
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: pre-commit
|
branch: pre-commit
|
||||||
title: "🤖 format everything with pre-commit by <${{ steps.generate-token.outputs.app-slug }}>"
|
title: "🤖 format everything with pre-commit by ${{ steps.setup-bot.outputs.app-slug }}"
|
||||||
body: |
|
body: |
|
||||||
Auto-generated by [create-pull-request][1] with **${{ steps.generate-token.outputs.app-slug }}**
|
Auto-generated by [create-pull-request][1] with **${{ steps.setup-bot.outputs.app-slug }}**
|
||||||
|
|
||||||
[1]: https://github.com/peter-evans/create-pull-request
|
[1]: https://github.com/peter-evans/create-pull-request
|
||||||
draft: false
|
draft: false
|
||||||
|
16
.github/workflows/push-docker.yml
vendored
16
.github/workflows/push-docker.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -30,25 +30,25 @@ 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@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.14
|
gradle-version: 8.14
|
||||||
|
|
||||||
- name: Run Gradle Command
|
- name: Run Gradle Command
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DISABLE_ADDITIONAL_FEATURES: true
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.4.1"
|
cosign-release: "v2.4.1"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
@ -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@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.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@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.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@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.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 }}
|
||||||
|
34
.github/workflows/releaseArtifacts.yml
vendored
34
.github/workflows/releaseArtifacts.yml
vendored
@ -13,17 +13,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
enable_security: [true, false]
|
disable_security: [true, false]
|
||||||
include:
|
include:
|
||||||
- enable_security: true
|
- disable_security: false
|
||||||
file_suffix: "-with-login"
|
file_suffix: "-with-login"
|
||||||
- enable_security: false
|
- disable_security: true
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -35,14 +35,14 @@ 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@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.14
|
gradle-version: 8.14
|
||||||
|
|
||||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
- name: Generate jar (Disable Security=${{ matrix.disable_security }})
|
||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.disable_security }}
|
||||||
STIRLING_PDF_DESKTOP_UI: false
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
@ -75,15 +75,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
enable_security: [true, false]
|
disable_security: [true, false]
|
||||||
include:
|
include:
|
||||||
- enable_security: true
|
- disable_security: false
|
||||||
file_suffix: "-with-login"
|
file_suffix: "-with-login"
|
||||||
- enable_security: false
|
- disable_security: true
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
run: cosign generate-key-pair
|
run: cosign generate-key-pair
|
||||||
@ -153,15 +153,15 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
enable_security: [true, false]
|
disable_security: [true, false]
|
||||||
include:
|
include:
|
||||||
- enable_security: true
|
- disable_security: false
|
||||||
file_suffix: "-with-login"
|
file_suffix: "-with-login"
|
||||||
- enable_security: false
|
- disable_security: true
|
||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ jobs:
|
|||||||
name: signed${{ matrix.file_suffix }}
|
name: signed${{ matrix.file_suffix }}
|
||||||
|
|
||||||
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ needs.build.outputs.version }}
|
tag_name: v${{ needs.build.outputs.version }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
6
.github/workflows/scorecards.yml
vendored
6
.github/workflows/scorecards.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
@ -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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
6
.github/workflows/sonarqube.yml
vendored
6
.github/workflows/sonarqube.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -27,13 +27,13 @@ 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@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
|
|
||||||
- name: Build and analyze with Gradle
|
- name: Build and analyze with Gradle
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
DOCKER_ENABLE_SECURITY: true
|
DISABLE_ADDITIONAL_FEATURES: false
|
||||||
STIRLING_PDF_DESKTOP_UI: true
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
run: |
|
run: |
|
||||||
./gradlew clean build sonar \
|
./gradlew clean build sonar \
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
4
.github/workflows/swagger.yml
vendored
4
.github/workflows/swagger.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -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@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
|
|
||||||
- name: Generate Swagger documentation
|
- name: Generate Swagger documentation
|
||||||
run: ./gradlew generateOpenApiDocs
|
run: ./gradlew generateOpenApiDocs
|
||||||
|
81
.github/workflows/sync_files.yml
vendored
81
.github/workflows/sync_files.yml
vendored
@ -8,87 +8,45 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "build.gradle"
|
- "build.gradle"
|
||||||
- "README.md"
|
- "README.md"
|
||||||
- "src/main/resources/messages_*.properties"
|
- "stirling-pdf/src/main/resources/messages_*.properties"
|
||||||
- "src/main/resources/static/3rdPartyLicenses.json"
|
- "stirling-pdf/src/main/resources/static/3rdPartyLicenses.json"
|
||||||
- "scripts/ignore_translation.toml"
|
- "scripts/ignore_translation.toml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
read_bot_entries:
|
sync-files:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
|
||||||
userName: ${{ steps.get-user-id.outputs.user_name }}
|
|
||||||
userEmail: ${{ steps.get-user-id.outputs.user_email }}
|
|
||||||
committer: ${{ steps.committer.outputs.committer }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
- name: Setup GitHub App Bot
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Get GitHub App User ID
|
|
||||||
id: get-user-id
|
|
||||||
run: |
|
|
||||||
USER_NAME="${{ steps.generate-token.outputs.app-slug }}[bot]"
|
|
||||||
USER_ID=$(gh api "/users/$USER_NAME" --jq .id)
|
|
||||||
USER_EMAIL="$USER_ID+$USER_NAME@users.noreply.github.com"
|
|
||||||
echo "user_name=$USER_NAME" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "user_email=$USER_EMAIL" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "user-id=$USER_ID" >> "$GITHUB_OUTPUT"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
|
||||||
|
|
||||||
- id: committer
|
|
||||||
run: |
|
|
||||||
COMMITTER="${{ steps.get-user-id.outputs.user_name }} <${{ steps.get-user-id.outputs.user_email }}>"
|
|
||||||
echo "committer=$COMMITTER" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
sync-files:
|
|
||||||
needs: ["read_bot_entries"]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
|
||||||
with:
|
|
||||||
app-id: ${{ vars.GH_APP_ID }}
|
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: 'pip' # caching pip dependencies
|
cache: "pip" # caching pip dependencies
|
||||||
|
|
||||||
- name: Sync translation property files
|
- name: Sync translation property files
|
||||||
run: |
|
run: |
|
||||||
python .github/scripts/check_language_properties.py --reference-file "src/main/resources/messages_en_GB.properties" --branch main
|
python .github/scripts/check_language_properties.py --reference-file "stirling-pdf/src/main/resources/messages_en_GB.properties" --branch main
|
||||||
|
|
||||||
- name: Set up git config
|
- name: Commit translation files
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name ${{ needs.read_bot_entries.outputs.userName }}
|
git add stirling-pdf/src/main/resources/messages_*.properties
|
||||||
git config --global user.email ${{ needs.read_bot_entries.outputs.userEmail }}
|
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "No changes detected"
|
||||||
|
|
||||||
- name: Run git add
|
|
||||||
run: |
|
|
||||||
git add src/main/resources/messages_*.properties
|
|
||||||
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||||
@ -100,15 +58,16 @@ jobs:
|
|||||||
- name: Run git add
|
- name: Run git add
|
||||||
run: |
|
run: |
|
||||||
git add README.md
|
git add README.md
|
||||||
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
|
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "No changes detected"
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
if: always()
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.setup-bot.outputs.token }}
|
||||||
commit-message: Update files
|
commit-message: Update files
|
||||||
committer: ${{ needs.read_bot_entries.outputs.committer }}
|
committer: ${{ steps.setup-bot.outputs.committer }}
|
||||||
author: ${{ needs.read_bot_entries.outputs.committer }}
|
author: ${{ steps.setup-bot.outputs.committer }}
|
||||||
signoff: true
|
signoff: true
|
||||||
branch: sync_readme
|
branch: sync_readme
|
||||||
title: ":globe_with_meridians: Sync Translations + Update README Progress Table"
|
title: ":globe_with_meridians: Sync Translations + Update README Progress Table"
|
||||||
@ -142,4 +101,4 @@ jobs:
|
|||||||
sign-commits: true
|
sign-commits: true
|
||||||
add-paths: |
|
add-paths: |
|
||||||
README.md
|
README.md
|
||||||
src/main/resources/messages_*.properties
|
stirling-pdf/src/main/resources/messages_*.properties
|
||||||
|
14
.github/workflows/testdriver.yml
vendored
14
.github/workflows/testdriver.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -28,10 +28,10 @@ jobs:
|
|||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: false
|
DISABLE_ADDITIONAL_FEATURES: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
@ -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@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
@ -76,7 +76,7 @@ jobs:
|
|||||||
- /stirling/test-${{ github.sha }}/config:/configs:rw
|
- /stirling/test-${{ github.sha }}/config:/configs:rw
|
||||||
- /stirling/test-${{ github.sha }}/logs:/logs:rw
|
- /stirling/test-${{ github.sha }}/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "false"
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||||
SECURITY_ENABLELOGIN: "false"
|
SECURITY_ENABLELOGIN: "false"
|
||||||
SYSTEM_DEFAULTLOCALE: en-GB
|
SYSTEM_DEFAULTLOCALE: en-GB
|
||||||
UI_APPNAME: "Stirling-PDF Test"
|
UI_APPNAME: "Stirling-PDF Test"
|
||||||
@ -105,7 +105,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -13,6 +13,7 @@ local.properties
|
|||||||
.recommenders
|
.recommenders
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
*.local.json
|
||||||
version.properties
|
version.properties
|
||||||
|
|
||||||
#### Stirling-PDF Files ###
|
#### Stirling-PDF Files ###
|
||||||
@ -124,6 +125,9 @@ SwaggerDoc.json
|
|||||||
*.rar
|
*.rar
|
||||||
*.db
|
*.db
|
||||||
/build
|
/build
|
||||||
|
/stirling-pdf/build
|
||||||
|
/common/build
|
||||||
|
/proprietary/build
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@ -193,4 +197,3 @@ id_ed25519.pub
|
|||||||
|
|
||||||
# node_modules
|
# node_modules
|
||||||
node_modules/
|
node_modules/
|
||||||
*.mjs
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.6
|
rev: v0.11.11
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
@ -20,9 +20,9 @@ repos:
|
|||||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
files: \.(html|css|js|py|md)$
|
files: \.(html|css|js|py|md)$
|
||||||
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
exclude: (.vscode|.devcontainer|stirling-pdf/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.24.3
|
rev: v8.26.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitleaks
|
- id: gitleaks
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -10,7 +10,7 @@
|
|||||||
"java.configuration.updateBuildConfiguration": "interactive",
|
"java.configuration.updateBuildConfiguration": "interactive",
|
||||||
"java.format.enabled": true,
|
"java.format.enabled": true,
|
||||||
"java.format.settings.profile": "GoogleStyle",
|
"java.format.settings.profile": "GoogleStyle",
|
||||||
"java.format.settings.google.version": "1.26.0",
|
"java.format.settings.google.version": "1.27.0",
|
||||||
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
||||||
// (DE) Aktiviert Kommentare im Java-Format.
|
// (DE) Aktiviert Kommentare im Java-Format.
|
||||||
// (EN) Enables comments in Java formatting.
|
// (EN) Enables comments in Java formatting.
|
||||||
@ -49,7 +49,11 @@
|
|||||||
".venv*/",
|
".venv*/",
|
||||||
".vscode/",
|
".vscode/",
|
||||||
"bin/",
|
"bin/",
|
||||||
|
"common/bin/",
|
||||||
|
"proprietary/bin/",
|
||||||
"build/",
|
"build/",
|
||||||
|
"common/build/",
|
||||||
|
"proprietary/build/",
|
||||||
"configs/",
|
"configs/",
|
||||||
"customFiles/",
|
"customFiles/",
|
||||||
"docs/",
|
"docs/",
|
||||||
@ -63,6 +67,8 @@
|
|||||||
".git-blame-ignore-revs",
|
".git-blame-ignore-revs",
|
||||||
".gitattributes",
|
".gitattributes",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
|
"common/.gitignore",
|
||||||
|
"proprietary/.gitignore",
|
||||||
".pre-commit-config.yaml",
|
".pre-commit-config.yaml",
|
||||||
],
|
],
|
||||||
// Enables signature help in Java.
|
// Enables signature help in Java.
|
||||||
@ -80,4 +86,9 @@
|
|||||||
"spring.initializr.defaultLanguage": "Java",
|
"spring.initializr.defaultLanguage": "Java",
|
||||||
"spring.initializr.defaultGroupId": "stirling.software.SPDF",
|
"spring.initializr.defaultGroupId": "stirling.software.SPDF",
|
||||||
"spring.initializr.defaultArtifactId": "SPDF",
|
"spring.initializr.defaultArtifactId": "SPDF",
|
||||||
|
"java.project.sourcePaths": [
|
||||||
|
"stirling-pdf/src/main/java",
|
||||||
|
"common/src/main/java",
|
||||||
|
"proprietary/src/main/java"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, do
|
|||||||
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
|
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
|
||||||
|
|
||||||
5. Add environment variable
|
5. Add environment variable
|
||||||
For local testing, you should generally be testing the full 'Security' version of Stirling-PDF. To do this, you must add the environment flag DOCKER_ENABLE_SECURITY=true to your system and/or IDE build/run step.
|
For local testing, you should generally be testing the full 'Security' version of Stirling PDF. To do this, you must add the environment flag DISABLE_ADDITIONAL_FEATURES=false to your system and/or IDE build/run step.
|
||||||
|
|
||||||
## 4. Project Structure
|
## 4. Project Structure
|
||||||
|
|
||||||
@ -114,9 +114,9 @@ Stirling-PDF offers several Docker versions:
|
|||||||
|
|
||||||
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory, such as:
|
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory, such as:
|
||||||
|
|
||||||
- `docker-compose-latest.yml`: Latest version without security features
|
- `docker-compose-latest.yml`: Latest version without login and security features
|
||||||
- `docker-compose-latest-security.yml`: Latest version with security features enabled
|
- `docker-compose-latest-security.yml`: Latest version with login and security features enabled
|
||||||
- `docker-compose-latest-fat-security.yml`: Fat version with security features enabled
|
- `docker-compose-latest-fat-security.yml`: Fat version with login and security features enabled
|
||||||
|
|
||||||
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
|
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
|
||||||
|
|
||||||
@ -137,11 +137,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
- /stirling/latest/config:/configs:rw
|
- ./stirling/latest/config:/configs:rw
|
||||||
- /stirling/latest/logs:/logs:rw
|
- ./stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "true"
|
||||||
PUID: 1002
|
PUID: 1002
|
||||||
PGID: 1002
|
PGID: 1002
|
||||||
@ -170,7 +170,7 @@ Stirling-PDF uses different Docker images for various configurations. The build
|
|||||||
1. Set the security environment variable:
|
1. Set the security environment variable:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export DOCKER_ENABLE_SECURITY=false # or true for security-enabled builds
|
export DISABLE_ADDITIONAL_FEATURES=true # or false for to enable login and security features for builds
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build the project with Gradle:
|
2. Build the project with Gradle:
|
||||||
@ -193,10 +193,10 @@ Stirling-PDF uses different Docker images for various configurations. The build
|
|||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
|
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
|
||||||
```
|
```
|
||||||
|
|
||||||
For the fat version (with security enabled):
|
For the fat version (with login and security features enabled):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export DOCKER_ENABLE_SECURITY=true
|
export DISABLE_ADDITIONAL_FEATURES=false
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
|
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -332,7 +332,7 @@ Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF
|
|||||||
|
|
||||||
### Thymeleaf overview
|
### Thymeleaf overview
|
||||||
|
|
||||||
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
|
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `stirling-pdf/src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
|
||||||
|
|
||||||
Some examples of this are:
|
Some examples of this are:
|
||||||
|
|
||||||
@ -384,7 +384,7 @@ This would generate n entries of tr for each person in exampleData
|
|||||||
### Adding a New Feature to the Backend (API)
|
### Adding a New Feature to the Backend (API)
|
||||||
|
|
||||||
1. **Create a New Controller:**
|
1. **Create a New Controller:**
|
||||||
- Create a new Java class in the `src/main/java/stirling/software/SPDF/controller/api` directory.
|
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/api` directory.
|
||||||
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
|
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
|
||||||
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
|
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
|
||||||
|
|
||||||
@ -411,7 +411,7 @@ This would generate n entries of tr for each person in exampleData
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. **Define the Service Layer:** (Not required but often useful)
|
2. **Define the Service Layer:** (Not required but often useful)
|
||||||
- Create a new service class in the `src/main/java/stirling/software/SPDF/service` directory.
|
- Create a new service class in the `stirling-pdf/src/main/java/stirling/software/SPDF/service` directory.
|
||||||
- Implement the business logic for the new feature.
|
- Implement the business logic for the new feature.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@ -463,7 +463,7 @@ This would generate n entries of tr for each person in exampleData
|
|||||||
### Adding a New Feature to the Frontend (UI)
|
### Adding a New Feature to the Frontend (UI)
|
||||||
|
|
||||||
1. **Create a New Thymeleaf Template:**
|
1. **Create a New Thymeleaf Template:**
|
||||||
- Create a new HTML file in the `src/main/resources/templates` directory.
|
- Create a new HTML file in the `stirling-pdf/src/main/resources/templates` directory.
|
||||||
- Use Thymeleaf attributes to dynamically generate content.
|
- Use Thymeleaf attributes to dynamically generate content.
|
||||||
- Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer.
|
- Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer.
|
||||||
|
|
||||||
@ -507,7 +507,7 @@ This would generate n entries of tr for each person in exampleData
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. **Create a New Controller for the UI:**
|
2. **Create a New Controller for the UI:**
|
||||||
- Create a new Java class in the `src/main/java/stirling/software/SPDF/controller/ui` directory.
|
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/ui` directory.
|
||||||
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
|
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@ -537,7 +537,7 @@ This would generate n entries of tr for each person in exampleData
|
|||||||
|
|
||||||
3. **Update the Navigation Bar:**
|
3. **Update the Navigation Bar:**
|
||||||
- Add a link to the new feature page in the navigation bar.
|
- Add a link to the new feature page in the navigation bar.
|
||||||
- Update the `src/main/resources/templates/fragments/navbar.html` file.
|
- Update the `stirling-pdf/src/main/resources/templates/fragments/navbar.html` file.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@ -551,7 +551,7 @@ When adding a new feature or modifying existing ones in Stirling-PDF, you'll nee
|
|||||||
|
|
||||||
### 1. Locate Existing Language Files
|
### 1. Locate Existing Language Files
|
||||||
|
|
||||||
Find the existing `messages.properties` files in the `src/main/resources` directory. You'll see files like:
|
Find the existing `messages.properties` files in the `stirling-pdf/src/main/resources` directory. You'll see files like:
|
||||||
|
|
||||||
- `messages.properties` (default, usually English)
|
- `messages.properties` (default, usually English)
|
||||||
- `messages_en_GB.properties`
|
- `messages_en_GB.properties`
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -1,12 +1,11 @@
|
|||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
#COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
COPY stirling-pdf/build/libs/*.jar app.jar
|
||||||
COPY build/libs/*.jar app.jar
|
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ LABEL org.opencontainers.image.version="${VERSION_TAG}"
|
|||||||
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
|
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
JAVA_CUSTOM_OPTS="" \
|
JAVA_CUSTOM_OPTS="" \
|
||||||
@ -73,7 +72,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
|||||||
py3-pillow@testing \
|
py3-pillow@testing \
|
||||||
py3-pdf2image@testing && \
|
py3-pdf2image@testing && \
|
||||||
python3 -m venv /opt/venv && \
|
python3 -m venv /opt/venv && \
|
||||||
/opt/venv/bin/pip install --upgrade pip && \
|
/opt/venv/bin/pip install --upgrade pip setuptools && \
|
||||||
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||||
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
|
@ -32,6 +32,7 @@ ENV SETUPTOOLS_USE_DISTUTILS=local
|
|||||||
# Installation der benötigten Python-Pakete
|
# Installation der benötigten Python-Pakete
|
||||||
RUN python3 -m venv --system-site-packages /opt/venv \
|
RUN python3 -m venv --system-site-packages /opt/venv \
|
||||||
&& . /opt/venv/bin/activate \
|
&& . /opt/venv/bin/activate \
|
||||||
|
&& pip install --upgrade pip setuptools \
|
||||||
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
|
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
|
||||||
|
|
||||||
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
|
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
|
||||||
|
@ -5,6 +5,9 @@ COPY build.gradle .
|
|||||||
COPY settings.gradle .
|
COPY settings.gradle .
|
||||||
COPY gradlew .
|
COPY gradlew .
|
||||||
COPY gradle gradle/
|
COPY gradle gradle/
|
||||||
|
COPY stirling-pdf/build.gradle stirling-pdf/.
|
||||||
|
COPY common/build.gradle common/.
|
||||||
|
COPY proprietary/build.gradle proprietary/.
|
||||||
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
||||||
|
|
||||||
# Set the working directory
|
# Set the working directory
|
||||||
@ -13,24 +16,24 @@ WORKDIR /app
|
|||||||
# Copy the entire project to the working directory
|
# Copy the entire project to the working directory
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
|
||||||
RUN DOCKER_ENABLE_SECURITY=true \
|
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
STIRLING_PDF_DESKTOP_UI=false \
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
COPY --from=build /app/build/libs/*.jar app.jar
|
COPY --from=build /app/stirling-pdf/build/libs/*.jar app.jar
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
JAVA_CUSTOM_OPTS="" \
|
JAVA_CUSTOM_OPTS="" \
|
||||||
@ -83,7 +86,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
|||||||
py3-pillow@testing \
|
py3-pillow@testing \
|
||||||
py3-pdf2image@testing && \
|
py3-pdf2image@testing && \
|
||||||
python3 -m venv /opt/venv && \
|
python3 -m venv /opt/venv && \
|
||||||
/opt/venv/bin/pip install --upgrade pip && \
|
/opt/venv/bin/pip install --upgrade pip setuptools && \
|
||||||
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||||
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
@ -18,7 +18,7 @@ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
|||||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY build/libs/*.jar app.jar
|
COPY stirling-pdf/build/libs/*.jar app.jar
|
||||||
|
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
|
@ -10,7 +10,7 @@ Fork Stirling-PDF and create a new branch out of `main`.
|
|||||||
|
|
||||||
Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
|
Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
|
||||||
|
|
||||||
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html)
|
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/templates/fragments/languages.html)
|
||||||
|
|
||||||
|
|
||||||
For example, to add Polish, you would add:
|
For example, to add Polish, you would add:
|
||||||
@ -25,7 +25,7 @@ The `data-bs-language-code` is the code used to reference the file in the next s
|
|||||||
|
|
||||||
Start by copying the existing English property file:
|
Start by copying the existing English property file:
|
||||||
|
|
||||||
- [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
|
- [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/messages_en_GB.properties)
|
||||||
|
|
||||||
Copy and rename it to `messages_{your data-bs-language-code here}.properties`. In the Polish example, you would set the name to `messages_pl_PL.properties`.
|
Copy and rename it to `messages_{your data-bs-language-code here}.properties`. In the Polish example, you would set the name to `messages_pl_PL.properties`.
|
||||||
|
|
||||||
@ -61,8 +61,16 @@ Make sure to place the entry under the correct language section. This helps main
|
|||||||
|
|
||||||
#### Windows command
|
#### Windows command
|
||||||
|
|
||||||
```ps
|
```powershell
|
||||||
python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --files src\main\resources\messages_pl_PL.properties
|
python .github/scripts/check_language_properties.py --reference-file stirling-pdf\src\main\resources\messages_en_GB.properties --branch "" --files stirling-pdf\src\main\resources\messages_pl_PL.properties
|
||||||
|
|
||||||
python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --check-file src\main\resources\messages_pl_PL.properties
|
python .github/scripts/check_language_properties.py --reference-file stirling-pdf\src\main\resources\messages_en_GB.properties --branch "" --check-file stirling-pdf\src\main\resources\messages_pl_PL.properties
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 .github/scripts/check_language_properties.py --reference-file stirling-pdf/src/main/resources/messages_en_GB.properties --branch "" --files stirling-pdf/src/main/resources/messages_pl_PL.properties
|
||||||
|
|
||||||
|
python3 .github/scripts/check_language_properties.py --reference-file stirling-pdf/src/main/resources/messages_en_GB.properties --branch "" --check-file stirling-pdf/src/main/resources/messages_pl_PL.properties
|
||||||
```
|
```
|
||||||
|
9
LICENSE
9
LICENSE
@ -1,6 +1,13 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 Stirling Tools
|
Copyright (c) 2025 Stirling PDF Inc.
|
||||||
|
|
||||||
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
|
* All content that resides under the "proprietary/" directory of this repository,
|
||||||
|
if that directory exists, is licensed under the license defined in "proprietary/LICENSE".
|
||||||
|
* Content outside of the above mentioned directories or restrictions above is
|
||||||
|
available under the MIT License as defined below.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
74
README.md
74
README.md
@ -116,47 +116,47 @@ Stirling-PDF currently supports 40 languages!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
| Tibetan (བོད་ཡིག་) (bo_CN) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
| Malayalam (മലയാളം) (ml_ML) |  |
|
| Malayalam (മലയാളം) (ml_IN) |  |
|
||||||
|
|
||||||
## Stirling PDF Enterprise
|
## Stirling PDF Enterprise
|
||||||
|
|
||||||
|
@ -124,10 +124,18 @@
|
|||||||
"moduleName": ".*",
|
"moduleName": ".*",
|
||||||
"moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0"
|
"moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": ".*",
|
||||||
|
"moduleLicense": "Eclipse Public License 1.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": ".*",
|
"moduleName": ".*",
|
||||||
"moduleLicense": "Eclipse Public License - v 1.0"
|
"moduleLicense": "Eclipse Public License - v 1.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": ".*",
|
||||||
|
"moduleLicense": "Eclipse Public License v2.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": ".*",
|
"moduleName": ".*",
|
||||||
"moduleLicense": "Eclipse Public License v. 2.0"
|
"moduleLicense": "Eclipse Public License v. 2.0"
|
||||||
|
469
build.gradle
469
build.gradle
@ -1,15 +1,15 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id 'jacoco'
|
id "jacoco"
|
||||||
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.springframework.boot" version "3.5.3"
|
||||||
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
id "edu.sc.seis.launch4j" version "3.0.6"
|
id "edu.sc.seis.launch4j" version "3.0.6"
|
||||||
id "com.diffplug.spotless" version "7.0.3"
|
id "com.diffplug.spotless" version "7.0.4"
|
||||||
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.2.0.5505"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,28 +19,166 @@ import java.nio.file.Files
|
|||||||
import java.time.Year
|
import java.time.Year
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = "3.4.5"
|
springBootVersion = "3.5.3"
|
||||||
pdfboxVersion = "3.0.5"
|
pdfboxVersion = "3.0.5"
|
||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.12.0"
|
||||||
lombokVersion = "1.18.38"
|
lombokVersion = "1.18.38"
|
||||||
bouncycastleVersion = "1.80"
|
bouncycastleVersion = "1.81"
|
||||||
springSecuritySamlVersion = "6.5.0"
|
springSecuritySamlVersion = "6.5.1"
|
||||||
openSamlVersion = "4.3.2"
|
openSamlVersion = "4.3.2"
|
||||||
|
commonmarkVersion = "0.24.0"
|
||||||
|
googleJavaFormatVersion = "1.27.0"
|
||||||
tempJrePath = null
|
tempJrePath = null
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
jar {
|
||||||
version = "0.46.2"
|
enabled = false
|
||||||
|
manifest {
|
||||||
java {
|
attributes "Implementation-Title": "Stirling-PDF",
|
||||||
// 17 is lowest but we support and recommend 21
|
"Implementation-Version": project.version
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
bootJar {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|
||||||
|
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
|
||||||
|
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
|
||||||
|
exclude 'stirling/software/proprietary/security/**'
|
||||||
|
}
|
||||||
|
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
|
||||||
|
exclude 'stirling/software/SPDF/UI/impl/**'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|
||||||
|
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
|
||||||
|
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
|
||||||
|
exclude 'stirling/software/proprietary/security/**'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
|
||||||
|
exclude 'stirling/software/SPDF/UI/impl/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = 'stirling.software'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
configurations.configureEach {
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tasks.register('writeVersion') {
|
||||||
|
def propsFile = file("$projectDir/common/src/main/resources/version.properties")
|
||||||
|
def propsDir = propsFile.parentFile
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
if (propsDir.exists()) {
|
||||||
|
if (propsFile.exists()) {
|
||||||
|
println "File exists: $propsFile"
|
||||||
|
} else {
|
||||||
|
println "$propsFile does not exist. Creating file."
|
||||||
|
propsFile.createNewFile()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println "Creating directory: $propsDir"
|
||||||
|
propsDir.mkdirs()
|
||||||
|
propsFile.createNewFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
def props = new Properties()
|
||||||
|
props.setProperty("version", version)
|
||||||
|
props.store(propsFile.newWriter(), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'com.diffplug.spotless'
|
||||||
|
apply plugin: 'org.springframework.boot'
|
||||||
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
|
||||||
|
java {
|
||||||
|
// 17 is lowest but we support and recommend 21
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = "https://build.shibboleth.net/maven/releases" }
|
}
|
||||||
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
|
||||||
|
configurations.configureEach {
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||||
|
// Exclude vulnerable BouncyCastle version used in tableau
|
||||||
|
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
|
||||||
|
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
|
||||||
|
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyManagement {
|
||||||
|
imports {
|
||||||
|
mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
implementation 'io.github.pixee:java-security-toolkit:1.2.2'
|
||||||
|
|
||||||
|
//tmp for security bumps
|
||||||
|
implementation 'ch.qos.logback:logback-core:1.5.18'
|
||||||
|
implementation 'ch.qos.logback:logback-classic:1.5.18'
|
||||||
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
dependsOn "spotlessApply"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava {
|
||||||
|
options.compilerArgs << "-parameters"
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("processResources") {
|
||||||
|
dependsOn(rootProject.tasks.writeVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
dependsOn "spotlessApply"
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@ -51,29 +189,14 @@ licenseReport {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
|
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|
||||||
exclude "stirling/software/SPDF/config/interfaces/DatabaseInterface.java"
|
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
|
||||||
exclude "stirling/software/SPDF/config/security/**"
|
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
|
||||||
exclude "stirling/software/SPDF/controller/api/DatabaseController.java"
|
exclude 'stirling/software/proprietary/security/**'
|
||||||
exclude "stirling/software/SPDF/controller/api/EmailController.java"
|
|
||||||
exclude "stirling/software/SPDF/controller/api/H2SQLCondition.java"
|
|
||||||
exclude "stirling/software/SPDF/controller/api/UserController.java"
|
|
||||||
exclude "stirling/software/SPDF/controller/web/AccountWebController.java"
|
|
||||||
exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java"
|
|
||||||
exclude "stirling/software/SPDF/model/api/Email.java"
|
|
||||||
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java"
|
|
||||||
exclude "stirling/software/SPDF/model/AttemptCounter.java"
|
|
||||||
exclude "stirling/software/SPDF/model/Authority.java"
|
|
||||||
exclude "stirling/software/SPDF/model/exception/BackupNotFoundException.java"
|
|
||||||
exclude "stirling/software/SPDF/model/exception/NoProviderFoundException.java"
|
|
||||||
exclude "stirling/software/SPDF/model/PersistentLogin.java"
|
|
||||||
exclude "stirling/software/SPDF/model/SessionEntity.java"
|
|
||||||
exclude "stirling/software/SPDF/model/User.java"
|
|
||||||
exclude "stirling/software/SPDF/repository/**"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
|
||||||
exclude "stirling/software/SPDF/UI/impl/**"
|
exclude 'stirling/software/SPDF/UI/impl/**'
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -81,15 +204,14 @@ sourceSets {
|
|||||||
|
|
||||||
test {
|
test {
|
||||||
java {
|
java {
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
|
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false' || System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true'
|
||||||
exclude "stirling/software/SPDF/config/security/**"
|
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
|
||||||
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationTokenTest.java"
|
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
|
||||||
exclude "stirling/software/SPDF/controller/api/EmailControllerTest.java"
|
exclude 'stirling/software/proprietary/security/**'
|
||||||
exclude "stirling/software/SPDF/repository/**"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
|
||||||
exclude "stirling/software/SPDF/UI/impl/**"
|
exclude 'stirling/software/SPDF/UI/impl/**'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,10 +237,9 @@ jpackage {
|
|||||||
mainJar = "Stirling-PDF-${project.version}.jar"
|
mainJar = "Stirling-PDF-${project.version}.jar"
|
||||||
appName = "Stirling PDF"
|
appName = "Stirling PDF"
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
// appVersion = "2005.45.1"
|
|
||||||
vendor = "Stirling PDF Inc"
|
vendor = "Stirling PDF Inc"
|
||||||
appDescription = "Stirling PDF - Your Local PDF Editor"
|
appDescription = "Stirling PDF - Your Local PDF Editor"
|
||||||
icon = "src/main/resources/static/favicon.ico"
|
icon = "stirling-pdf/src/main/resources/static/favicon.ico"
|
||||||
verbose = true
|
verbose = true
|
||||||
// mainClass = "org.springframework.boot.loader.launch.JarLauncher"
|
// mainClass = "org.springframework.boot.loader.launch.JarLauncher"
|
||||||
|
|
||||||
@ -126,6 +247,7 @@ jpackage {
|
|||||||
javaOptions = [
|
javaOptions = [
|
||||||
"-DBROWSER_OPEN=true",
|
"-DBROWSER_OPEN=true",
|
||||||
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
||||||
|
"-DDISABLE_ADDITIONAL_FEATURES=false",
|
||||||
"-Djava.awt.headless=false",
|
"-Djava.awt.headless=false",
|
||||||
"-Dapple.awt.UIElement=true",
|
"-Dapple.awt.UIElement=true",
|
||||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||||
@ -156,10 +278,10 @@ jpackage {
|
|||||||
installDir = "C:/Program Files/Stirling-PDF"
|
installDir = "C:/Program Files/Stirling-PDF"
|
||||||
}
|
}
|
||||||
|
|
||||||
// macOS-specific configuration
|
// MacOS-specific configuration
|
||||||
mac {
|
mac {
|
||||||
appVersion = getMacVersion(project.version.toString())
|
appVersion = getMacVersion(project.version.toString())
|
||||||
icon = "src/main/resources/static/favicon.icns"
|
icon = "stirling-pdf/src/main/resources/static/favicon.icns"
|
||||||
type = "dmg"
|
type = "dmg"
|
||||||
macPackageIdentifier = "Stirling PDF"
|
macPackageIdentifier = "Stirling PDF"
|
||||||
macPackageName = "Stirling PDF"
|
macPackageName = "Stirling PDF"
|
||||||
@ -181,7 +303,7 @@ jpackage {
|
|||||||
// Linux-specific configuration
|
// Linux-specific configuration
|
||||||
linux {
|
linux {
|
||||||
appVersion = project.version
|
appVersion = project.version
|
||||||
icon = "src/main/resources/static/favicon.png"
|
icon = "stirling-pdf/src/main/resources/static/favicon.png"
|
||||||
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
||||||
|
|
||||||
// Debian package configuration
|
// Debian package configuration
|
||||||
@ -217,10 +339,15 @@ jpackage {
|
|||||||
]*/
|
]*/
|
||||||
|
|
||||||
// Add copyright and license information
|
// Add copyright and license information
|
||||||
copyright = "Copyright © 2024 Stirling Software"
|
copyright = "Copyright © 2025 Stirling PDF Inc."
|
||||||
licenseFile = "LICENSE"
|
licenseFile = "LICENSE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//tasks.wrapper {
|
||||||
|
// gradleVersion = "8.14"
|
||||||
|
// distributionType = Wrapper.DistributionType.ALL
|
||||||
|
//}
|
||||||
|
|
||||||
tasks.register('jpackageMacX64') {
|
tasks.register('jpackageMacX64') {
|
||||||
group = 'distribution'
|
group = 'distribution'
|
||||||
description = 'Packages app for MacOS x86_64'
|
description = 'Packages app for MacOS x86_64'
|
||||||
@ -253,7 +380,7 @@ tasks.register('jpackageMacX64') {
|
|||||||
'--main-class', 'org.springframework.boot.loader.launch.JarLauncher',
|
'--main-class', 'org.springframework.boot.loader.launch.JarLauncher',
|
||||||
'--runtime-image', file(jrePath + "/zulu-17.jre/Contents/Home"),
|
'--runtime-image', file(jrePath + "/zulu-17.jre/Contents/Home"),
|
||||||
'--dest', 'build/jpackage/x86_64',
|
'--dest', 'build/jpackage/x86_64',
|
||||||
'--icon', 'src/main/resources/static/favicon.icns',
|
'--icon', 'stirling-pdf/src/main/resources/static/favicon.icns',
|
||||||
'--app-version', getMacVersion(project.version.toString()),
|
'--app-version', getMacVersion(project.version.toString()),
|
||||||
'--mac-package-name', 'Stirling PDF (x86_64)',
|
'--mac-package-name', 'Stirling PDF (x86_64)',
|
||||||
'--mac-package-identifier', 'Stirling PDF (x86_64)',
|
'--mac-package-identifier', 'Stirling PDF (x86_64)',
|
||||||
@ -262,6 +389,7 @@ tasks.register('jpackageMacX64') {
|
|||||||
// Java options
|
// Java options
|
||||||
'--java-options', '-DBROWSER_OPEN=true',
|
'--java-options', '-DBROWSER_OPEN=true',
|
||||||
'--java-options', '-DSTIRLING_PDF_DESKTOP_UI=true',
|
'--java-options', '-DSTIRLING_PDF_DESKTOP_UI=true',
|
||||||
|
'--java-options', '-DDISABLE_ADDITIONAL_FEATURES=false',
|
||||||
'--java-options', '-Djava.awt.headless=false',
|
'--java-options', '-Djava.awt.headless=false',
|
||||||
'--java-options', '-Dapple.awt.UIElement=true',
|
'--java-options', '-Dapple.awt.UIElement=true',
|
||||||
'--java-options', '--add-opens=java.base/java.lang=ALL-UNNAMED',
|
'--java-options', '--add-opens=java.base/java.lang=ALL-UNNAMED',
|
||||||
@ -290,8 +418,6 @@ tasks.register('jpackageMacX64') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//jpackage.finalizedBy(jpackageMacX64)
|
|
||||||
|
|
||||||
tasks.register('downloadTempJre') {
|
tasks.register('downloadTempJre') {
|
||||||
group = 'distribution'
|
group = 'distribution'
|
||||||
description = 'Downloads and extracts a temporary JRE'
|
description = 'Downloads and extracts a temporary JRE'
|
||||||
@ -303,18 +429,18 @@ tasks.register('downloadTempJre') {
|
|||||||
def jreArchive = new File(tmpDir, 'jre.tar.gz')
|
def jreArchive = new File(tmpDir, 'jre.tar.gz')
|
||||||
def jreDir = new File(tmpDir, 'jre')
|
def jreDir = new File(tmpDir, 'jre')
|
||||||
|
|
||||||
println "🔽 Downloading JRE to $jreArchive..."
|
println "Downloading JRE to $jreArchive"
|
||||||
jreArchive.withOutputStream { out ->
|
jreArchive.withOutputStream { out ->
|
||||||
new URI(jreUrl).toURL().withInputStream { from -> out << from }
|
new URI(jreUrl).toURL().withInputStream { from -> out << from }
|
||||||
}
|
}
|
||||||
|
|
||||||
println "📦 Extracting JRE to $jreDir..."
|
println "Extracting JRE to $jreDir"
|
||||||
jreDir.mkdirs()
|
jreDir.mkdirs()
|
||||||
providers.exec {
|
providers.exec {
|
||||||
commandLine 'tar', '-xzf', jreArchive.absolutePath, '-C', jreDir.absolutePath, '--strip-components=1'
|
commandLine 'tar', '-xzf', jreArchive.absolutePath, '-C', jreDir.absolutePath, '--strip-components=1'
|
||||||
}.result.get()
|
}.result.get()
|
||||||
|
|
||||||
println "✅ JRE ready at: $jreDir"
|
println "JRE ready at: $jreDir"
|
||||||
ext.tempJrePath = jreDir.absolutePath
|
ext.tempJrePath = jreDir.absolutePath
|
||||||
project.ext.tempJrePath = jreDir.absolutePath
|
project.ext.tempJrePath = jreDir.absolutePath
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -340,7 +466,7 @@ tasks.register('cleanTempJre') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch4j {
|
launch4j {
|
||||||
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
icon = "${projectDir}/stirling-pdf/src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
outfile="Stirling-PDF.exe"
|
outfile="Stirling-PDF.exe"
|
||||||
|
|
||||||
@ -351,7 +477,7 @@ launch4j {
|
|||||||
}
|
}
|
||||||
jarTask = tasks.bootJar
|
jarTask = tasks.bootJar
|
||||||
|
|
||||||
errTitle="Encountered error, Do you have Java 21?"
|
errTitle="Encountered error, do you have Java 21?"
|
||||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||||
|
|
||||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
@ -374,9 +500,12 @@ launch4j {
|
|||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
java {
|
java {
|
||||||
target project.fileTree('src').include('**/*.java')
|
target sourceSets.main.allJava
|
||||||
|
target project(':common').sourceSets.main.allJava
|
||||||
|
target project(':proprietary').sourceSets.main.allJava
|
||||||
|
target project(':stirling-pdf').sourceSets.main.allJava
|
||||||
|
|
||||||
googleJavaFormat("1.26.0").aosp().reorderImports(false)
|
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
||||||
|
|
||||||
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
@ -391,190 +520,12 @@ sonar {
|
|||||||
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
|
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
|
||||||
property "sonar.organization", "stirling-tools"
|
property "sonar.organization", "stirling-tools"
|
||||||
|
|
||||||
property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
property "sonar.exclusions", "**/build-wrapper-dump.json, **/src/main/java/org/apache/**, **/src/main/resources/static/pdfjs/**, **/src/main/resources/static/pdfjs-legacy/**, **/src/main/resources/static/js/thirdParty/**"
|
||||||
property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
property "sonar.coverage.exclusions", "**/src/main/java/org/apache/**, **/src/main/resources/static/pdfjs/**, **/src/main/resources/static/pdfjs-legacy/**, **/src/main/resources/static/js/thirdParty/**"
|
||||||
property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
property "sonar.cpd.exclusions", "**/src/main/java/org/apache/**, **/src/main/resources/static/pdfjs/**, **/src/main/resources/static/pdfjs-legacy/**, **/src/main/resources/static/js/thirdParty/**"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//gradleLint {
|
|
||||||
// rules=['unused-dependency']
|
|
||||||
// }
|
|
||||||
tasks.wrapper {
|
|
||||||
gradleVersion = "8.14"
|
|
||||||
distributionType = Wrapper.DistributionType.ALL
|
|
||||||
}
|
|
||||||
//tasks.withType(JavaCompile) {
|
|
||||||
// options.compilerArgs << "-Xlint:deprecation"
|
|
||||||
//}
|
|
||||||
configurations.all {
|
|
||||||
// Remove all commons-logging dependencies so that only spring-jcl is used
|
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
|
||||||
// Exclude Tomcat
|
|
||||||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
//tmp for security bumps
|
|
||||||
implementation 'ch.qos.logback:logback-core:1.5.18'
|
|
||||||
implementation 'ch.qos.logback:logback-classic:1.5.18'
|
|
||||||
|
|
||||||
|
|
||||||
// Exclude vulnerable BouncyCastle version used in tableau
|
|
||||||
configurations.all {
|
|
||||||
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
|
|
||||||
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
|
|
||||||
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
|
||||||
implementation "me.friwi:jcefmaven:132.3.1"
|
|
||||||
implementation "org.openjfx:javafx-controls:21"
|
|
||||||
implementation "org.openjfx:javafx-swing:21"
|
|
||||||
}
|
|
||||||
|
|
||||||
//security updates
|
|
||||||
implementation "org.springframework:spring-webmvc:6.2.7"
|
|
||||||
|
|
||||||
implementation("io.github.pixee:java-security-toolkit:1.2.1")
|
|
||||||
|
|
||||||
// Exclude Tomcat and include Jetty
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
|
||||||
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
|
||||||
implementation 'com.posthog.java:posthog:1.2.0'
|
|
||||||
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
|
||||||
|
|
||||||
|
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
|
||||||
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
||||||
implementation 'io.micrometer:micrometer-registry-prometheus'
|
|
||||||
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
|
||||||
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE"
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-mail:$springBootVersion"
|
|
||||||
|
|
||||||
implementation "org.springframework.session:spring-session-core:3.4.3"
|
|
||||||
implementation "org.springframework:spring-jdbc:6.2.7"
|
|
||||||
|
|
||||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
|
||||||
// Don't upgrade h2database
|
|
||||||
runtimeOnly "com.h2database:h2:2.3.232"
|
|
||||||
runtimeOnly "org.postgresql:postgresql:42.7.5"
|
|
||||||
constraints {
|
|
||||||
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
|
||||||
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
|
||||||
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
|
||||||
}
|
|
||||||
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
|
||||||
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
|
||||||
implementation 'com.coveo:saml-client:5.0.0'
|
|
||||||
|
|
||||||
}
|
|
||||||
implementation 'org.snakeyaml:snakeyaml-engine:2.9'
|
|
||||||
|
|
||||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
|
||||||
|
|
||||||
// Batik
|
|
||||||
implementation "org.apache.xmlgraphics:batik-all:1.19"
|
|
||||||
|
|
||||||
// TwelveMonkeys
|
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
|
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-bmp:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-hdr:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-icns:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-iff:$imageioVersion"
|
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-jpeg:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
|
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-tiff:$imageioVersion"
|
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
|
||||||
|
|
||||||
// Image metadata extractor
|
|
||||||
implementation "com.drewnoakes:metadata-extractor:2.19.0"
|
|
||||||
|
|
||||||
implementation "commons-io:commons-io:2.19.0"
|
|
||||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8"
|
|
||||||
//general PDF
|
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.opencsv/opencsv
|
|
||||||
implementation ("com.opencsv:opencsv:5.11")
|
|
||||||
|
|
||||||
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion")
|
|
||||||
implementation "org.apache.pdfbox:preflight:$pdfboxVersion"
|
|
||||||
|
|
||||||
|
|
||||||
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion")
|
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/technology.tabula/tabula
|
|
||||||
implementation ('technology.tabula:tabula:1.0.5') {
|
|
||||||
exclude group: "org.slf4j", module: "slf4j-simple"
|
|
||||||
exclude group: "org.bouncycastle", module: "bcprov-jdk15on"
|
|
||||||
exclude group: "com.google.code.gson", module: "gson"
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
|
|
||||||
|
|
||||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
|
||||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
|
||||||
implementation "io.micrometer:micrometer-core:1.15.0"
|
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
|
||||||
implementation "org.commonmark:commonmark:0.24.0"
|
|
||||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.24.0"
|
|
||||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
|
||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
|
||||||
implementation "com.fathzer:javaluator:3.0.6"
|
|
||||||
|
|
||||||
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
|
||||||
compileOnly "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'
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
|
||||||
options.encoding = "UTF-8"
|
|
||||||
dependsOn "spotlessApply"
|
|
||||||
}
|
|
||||||
compileJava {
|
|
||||||
options.compilerArgs << "-parameters"
|
|
||||||
}
|
|
||||||
|
|
||||||
task writeVersion {
|
|
||||||
def propsFile = file("$projectDir/src/main/resources/version.properties")
|
|
||||||
def propsDir = propsFile.parentFile
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
if (!propsDir.exists()) {
|
|
||||||
propsDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
def props = new Properties()
|
|
||||||
props.setProperty("version", version)
|
|
||||||
props.store(propsFile.newWriter(), null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources.dependsOn(writeVersion)
|
|
||||||
|
|
||||||
swaggerhubUpload {
|
swaggerhubUpload {
|
||||||
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
|
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
|
||||||
api = "Stirling-PDF" // The name of your API on SwaggerHub
|
api = "Stirling-PDF" // The name of your API on SwaggerHub
|
||||||
@ -585,25 +536,26 @@ swaggerhubUpload {
|
|||||||
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
|
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
dependencies {
|
||||||
enabled = false
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
manifest {
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
|
||||||
attributes "Implementation-Title": "Stirling-PDF",
|
|
||||||
"Implementation-Version": project.version
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("test") {
|
tasks.named("test") {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
task printVersion {
|
|
||||||
|
// Make sure all relevant processes depend on writeVersion
|
||||||
|
processResources.dependsOn(writeVersion)
|
||||||
|
|
||||||
|
tasks.register('printVersion') {
|
||||||
doLast {
|
doLast {
|
||||||
println project.version
|
println project.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task printMacVersion {
|
tasks.register('printMacVersion') {
|
||||||
doLast {
|
doLast {
|
||||||
println getMacVersion(project.version.toString())
|
println getMacVersion(project.version.toString())
|
||||||
}
|
}
|
||||||
@ -612,3 +564,22 @@ task printMacVersion {
|
|||||||
tasks.named('generateOpenApiDocs') {
|
tasks.named('generateOpenApiDocs') {
|
||||||
doNotTrackState("Tracking state is not supported for this task")
|
doNotTrackState("Tracking state is not supported for this task")
|
||||||
}
|
}
|
||||||
|
tasks.named('bootRun') {
|
||||||
|
group = 'application'
|
||||||
|
description = 'Delegates to :stirling-pdf:bootRun'
|
||||||
|
dependsOn ':stirling-pdf:bootRun'
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
println "Delegating to :stirling-pdf:bootRun"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('build') {
|
||||||
|
group = 'build'
|
||||||
|
description = 'Delegates to :stirling-pdf:bootJar'
|
||||||
|
dependsOn ':stirling-pdf:bootJar'
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
println "Delegating to :stirling-pdf:bootJar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
196
common/.gitignore
vendored
Normal file
196
common/.gitignore
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
### Eclipse ###
|
||||||
|
.metadata
|
||||||
|
bin/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.exe
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.recommenders
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
version.properties
|
||||||
|
|
||||||
|
#### Stirling-PDF Files ###
|
||||||
|
pipeline/watchedFolders/
|
||||||
|
pipeline/finishedFolders/
|
||||||
|
customFiles/
|
||||||
|
configs/
|
||||||
|
watchedFolders/
|
||||||
|
clientWebUI/
|
||||||
|
!cucumber/
|
||||||
|
!cucumber/exampleFiles/
|
||||||
|
!cucumber/exampleFiles/example_html.zip
|
||||||
|
exampleYmlFiles/stirling/
|
||||||
|
/testing/file_snapshots
|
||||||
|
SwaggerDoc.json
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.gradle
|
||||||
|
.lock
|
||||||
|
|
||||||
|
# External tool builders
|
||||||
|
.externalToolBuilders/
|
||||||
|
|
||||||
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# PyDev specific (Python IDE for Eclipse)
|
||||||
|
*.pydevproject
|
||||||
|
|
||||||
|
# CDT-specific (C/C++ Development Tooling)
|
||||||
|
.cproject
|
||||||
|
|
||||||
|
# CDT- autotools
|
||||||
|
.autotools
|
||||||
|
|
||||||
|
# Java annotation processor (APT)
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# PDT-specific (PHP Development Tools)
|
||||||
|
.buildpath
|
||||||
|
|
||||||
|
# sbteclipse plugin
|
||||||
|
.target
|
||||||
|
|
||||||
|
# Tern plugin
|
||||||
|
.tern-project
|
||||||
|
|
||||||
|
# TeXlipse plugin
|
||||||
|
.texlipse
|
||||||
|
|
||||||
|
# STS (Spring Tool Suite)
|
||||||
|
.springBeans
|
||||||
|
|
||||||
|
# Code Recommenders
|
||||||
|
.recommenders/
|
||||||
|
|
||||||
|
# Annotation Processing
|
||||||
|
.apt_generated/
|
||||||
|
.apt_generated_test/
|
||||||
|
|
||||||
|
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||||
|
.cache-main
|
||||||
|
.scala_dependencies
|
||||||
|
.worksheet
|
||||||
|
|
||||||
|
# Uncomment this line if you wish to ignore the project description file.
|
||||||
|
# Typically, this file would be tracked if it contains build/dependency configurations:
|
||||||
|
#.project
|
||||||
|
|
||||||
|
### Eclipse Patch ###
|
||||||
|
# Spring Boot Tooling
|
||||||
|
.sts4-cache/
|
||||||
|
|
||||||
|
### Git ###
|
||||||
|
# Created by git for backups. To disable backups in Git:
|
||||||
|
# $ git config --global mergetool.keepBackup false
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Created by git when using merge tools for conflicts
|
||||||
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
|
*.LOCAL.*
|
||||||
|
*.REMOTE.*
|
||||||
|
*_BACKUP_*.txt
|
||||||
|
*_BASE_*.txt
|
||||||
|
*_LOCAL_*.txt
|
||||||
|
*_REMOTE_*.txt
|
||||||
|
|
||||||
|
### Java ###
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
*.db
|
||||||
|
/build
|
||||||
|
/common/build/
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.env*
|
||||||
|
.venv*
|
||||||
|
env*/
|
||||||
|
venv*/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
/.vscode/**/*
|
||||||
|
!/.vscode/settings.json
|
||||||
|
!/.vscode/extensions.json
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Ignore Mac DS_Store files
|
||||||
|
.DS_Store
|
||||||
|
**/.DS_Store
|
||||||
|
|
||||||
|
# cucumber
|
||||||
|
/cucumber/reports/**
|
||||||
|
|
||||||
|
# Certs and Security Files
|
||||||
|
*.p12
|
||||||
|
*.pk8
|
||||||
|
*.pem
|
||||||
|
*.crt
|
||||||
|
*.cer
|
||||||
|
*.cert
|
||||||
|
*.der
|
||||||
|
*.key
|
||||||
|
*.csr
|
||||||
|
*.kdbx
|
||||||
|
*.jks
|
||||||
|
*.asc
|
||||||
|
|
||||||
|
# SSH Keys
|
||||||
|
*.pub
|
||||||
|
*.priv
|
||||||
|
id_rsa
|
||||||
|
id_rsa.pub
|
||||||
|
id_ecdsa
|
||||||
|
id_ecdsa.pub
|
||||||
|
id_ed25519
|
||||||
|
id_ed25519.pub
|
||||||
|
.ssh/
|
||||||
|
*ssh
|
||||||
|
|
||||||
|
# cache
|
||||||
|
.cache
|
||||||
|
.ruff_cache
|
||||||
|
.mypy_cache
|
||||||
|
.pytest_cache
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
**/jcef-bundle/
|
||||||
|
|
||||||
|
# node_modules
|
||||||
|
node_modules/
|
32
common/build.gradle
Normal file
32
common/build.gradle
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Configure bootRun to disable it or point to a main class
|
||||||
|
bootRun {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
spotless {
|
||||||
|
java {
|
||||||
|
target sourceSets.main.allJava
|
||||||
|
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
||||||
|
|
||||||
|
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
||||||
|
toggleOffOn()
|
||||||
|
trimTrailingWhitespace()
|
||||||
|
leadingTabsToSpaces()
|
||||||
|
endWithNewline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
|
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||||
|
api 'com.fathzer:javaluator:3.0.6'
|
||||||
|
api 'com.posthog.java:posthog:1.2.0'
|
||||||
|
api 'org.apache.commons:commons-lang3:3.17.0'
|
||||||
|
api 'com.drewnoakes:metadata-extractor:2.19.0' // Image metadata extractor
|
||||||
|
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
||||||
|
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
|
||||||
|
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
||||||
|
api 'org.snakeyaml:snakeyaml-engine:2.9'
|
||||||
|
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
|
||||||
|
api 'jakarta.mail:jakarta.mail-api:2.1.3'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-aop'
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package stirling.software.common.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for a POST endpoint that is executed through the Stirling "auto‑job" framework.
|
||||||
|
* <p>
|
||||||
|
* Behaviour notes:
|
||||||
|
* <ul>
|
||||||
|
* <li>The endpoint is registered with {@code POST} and, by default, consumes
|
||||||
|
* {@code multipart/form-data} unless you override {@link #consumes()}.</li>
|
||||||
|
* <li>When the client supplies {@code ?async=true} the call is handed to
|
||||||
|
* {@link stirling.software.common.service.JobExecutorService JobExecutorService} where it may
|
||||||
|
* be queued, retried, tracked and subject to time‑outs. For synchronous (default)
|
||||||
|
* invocations these advanced options are ignored.</li>
|
||||||
|
* <li>Progress information (see {@link #trackProgress()}) is stored in
|
||||||
|
* {@link stirling.software.common.service.TaskManager TaskManager} and can be
|
||||||
|
* polled via <code>GET /api/v1/general/job/{id}</code>.</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>Unless stated otherwise an attribute only affects <em>async</em> execution.</p>
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@RequestMapping(method = RequestMethod.POST)
|
||||||
|
public @interface AutoJobPostMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#value} – the path mapping of the endpoint.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class, attribute = "value")
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME types this endpoint accepts. Defaults to {@code multipart/form-data}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class, attribute = "consumes")
|
||||||
|
String[] consumes() default {"multipart/form-data"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum execution time in milliseconds before the job is aborted.
|
||||||
|
* A negative value means "use the application default".
|
||||||
|
* <p>Only honoured when {@code async=true}.</p>
|
||||||
|
*/
|
||||||
|
long timeout() default -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total number of attempts (initial + retries). Must be at least 1.
|
||||||
|
* Retries are executed with exponential back‑off.
|
||||||
|
* <p>Only honoured when {@code async=true}.</p>
|
||||||
|
*/
|
||||||
|
int retryCount() default 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record percentage / note updates so they can be retrieved via the REST status endpoint.
|
||||||
|
* <p>Only honoured when {@code async=true}.</p>
|
||||||
|
*/
|
||||||
|
boolean trackProgress() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@code true} the job may be placed in a queue instead of being rejected when resources
|
||||||
|
* are scarce.
|
||||||
|
* <p>Only honoured when {@code async=true}.</p>
|
||||||
|
*/
|
||||||
|
boolean queueable() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relative resource weight (1–100) used by the scheduler to prioritise / throttle jobs. Values
|
||||||
|
* below 1 are clamped to 1, values above 100 to 100.
|
||||||
|
*/
|
||||||
|
int resourceWeight() default 50;
|
||||||
|
}
|
@ -0,0 +1,365 @@
|
|||||||
|
package stirling.software.common.aop;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.*;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
import stirling.software.common.service.FileOrUploadService;
|
||||||
|
import stirling.software.common.service.FileStorage;
|
||||||
|
import stirling.software.common.service.JobExecutorService;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Order(0) // Highest precedence - executes before audit aspects
|
||||||
|
public class AutoJobAspect {
|
||||||
|
|
||||||
|
private static final Duration RETRY_BASE_DELAY = Duration.ofMillis(100);
|
||||||
|
|
||||||
|
private final JobExecutorService jobExecutorService;
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
private final FileOrUploadService fileOrUploadService;
|
||||||
|
private final FileStorage fileStorage;
|
||||||
|
|
||||||
|
@Around("@annotation(autoJobPostMapping)")
|
||||||
|
public Object wrapWithJobExecution(
|
||||||
|
ProceedingJoinPoint joinPoint, AutoJobPostMapping autoJobPostMapping) {
|
||||||
|
// This aspect will run before any audit aspects due to @Order(0)
|
||||||
|
// Extract parameters from the request and annotation
|
||||||
|
boolean async = Boolean.parseBoolean(request.getParameter("async"));
|
||||||
|
long timeout = autoJobPostMapping.timeout();
|
||||||
|
int retryCount = autoJobPostMapping.retryCount();
|
||||||
|
boolean trackProgress = autoJobPostMapping.trackProgress();
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"AutoJobPostMapping execution with async={}, timeout={}, retryCount={}, trackProgress={}",
|
||||||
|
async,
|
||||||
|
timeout > 0 ? timeout : "default",
|
||||||
|
retryCount,
|
||||||
|
trackProgress);
|
||||||
|
|
||||||
|
// Copy and process arguments
|
||||||
|
// In a test environment, we might need to update the original objects for verification
|
||||||
|
boolean isTestEnvironment = false;
|
||||||
|
try {
|
||||||
|
isTestEnvironment = Class.forName("org.junit.jupiter.api.Test") != null;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Not in a test environment
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] args =
|
||||||
|
isTestEnvironment
|
||||||
|
? processArgsInPlace(joinPoint.getArgs(), async)
|
||||||
|
: copyAndProcessArgs(joinPoint.getArgs(), async);
|
||||||
|
|
||||||
|
// Extract queueable and resourceWeight parameters and validate
|
||||||
|
boolean queueable = autoJobPostMapping.queueable();
|
||||||
|
int resourceWeight = Math.max(1, Math.min(100, autoJobPostMapping.resourceWeight()));
|
||||||
|
|
||||||
|
// Integrate with the JobExecutorService
|
||||||
|
if (retryCount <= 1) {
|
||||||
|
// No retries needed, simple execution
|
||||||
|
return jobExecutorService.runJobGeneric(
|
||||||
|
async,
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
// Note: Progress tracking is handled in TaskManager/JobExecutorService
|
||||||
|
// The trackProgress flag controls whether detailed progress is stored
|
||||||
|
// for REST API queries, not WebSocket notifications
|
||||||
|
return joinPoint.proceed(args);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
log.error(
|
||||||
|
"AutoJobAspect caught exception during job execution: {}",
|
||||||
|
ex.getMessage(),
|
||||||
|
ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
queueable,
|
||||||
|
resourceWeight);
|
||||||
|
} else {
|
||||||
|
// Use retry logic
|
||||||
|
return executeWithRetries(
|
||||||
|
joinPoint,
|
||||||
|
args,
|
||||||
|
async,
|
||||||
|
timeout,
|
||||||
|
retryCount,
|
||||||
|
trackProgress,
|
||||||
|
queueable,
|
||||||
|
resourceWeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object executeWithRetries(
|
||||||
|
ProceedingJoinPoint joinPoint,
|
||||||
|
Object[] args,
|
||||||
|
boolean async,
|
||||||
|
long timeout,
|
||||||
|
int maxRetries,
|
||||||
|
boolean trackProgress,
|
||||||
|
boolean queueable,
|
||||||
|
int resourceWeight) {
|
||||||
|
|
||||||
|
// Keep jobId reference for progress tracking in TaskManager
|
||||||
|
AtomicReference<String> jobIdRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
return jobExecutorService.runJobGeneric(
|
||||||
|
async,
|
||||||
|
() -> {
|
||||||
|
// Use iterative approach instead of recursion to avoid stack overflow
|
||||||
|
Throwable lastException = null;
|
||||||
|
|
||||||
|
// Attempt counter starts at 1 for first try
|
||||||
|
for (int currentAttempt = 1; currentAttempt <= maxRetries; currentAttempt++) {
|
||||||
|
try {
|
||||||
|
if (trackProgress && async) {
|
||||||
|
// Get jobId for progress tracking in TaskManager
|
||||||
|
// This enables REST API progress queries, not WebSocket
|
||||||
|
if (jobIdRef.get() == null) {
|
||||||
|
jobIdRef.set(getJobIdFromContext());
|
||||||
|
}
|
||||||
|
String jobId = jobIdRef.get();
|
||||||
|
if (jobId != null) {
|
||||||
|
log.debug(
|
||||||
|
"Tracking progress for job {} (attempt {}/{})",
|
||||||
|
jobId,
|
||||||
|
currentAttempt,
|
||||||
|
maxRetries);
|
||||||
|
// Progress is tracked in TaskManager for REST API access
|
||||||
|
// No WebSocket notifications sent here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to execute the operation
|
||||||
|
return joinPoint.proceed(args);
|
||||||
|
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
lastException = ex;
|
||||||
|
log.error(
|
||||||
|
"AutoJobAspect caught exception during job execution (attempt {}/{}): {}",
|
||||||
|
currentAttempt,
|
||||||
|
maxRetries,
|
||||||
|
ex.getMessage(),
|
||||||
|
ex);
|
||||||
|
|
||||||
|
// Check if we should retry
|
||||||
|
if (currentAttempt < maxRetries) {
|
||||||
|
log.info(
|
||||||
|
"Retrying operation, attempt {}/{}",
|
||||||
|
currentAttempt + 1,
|
||||||
|
maxRetries);
|
||||||
|
|
||||||
|
if (trackProgress && async) {
|
||||||
|
String jobId = jobIdRef.get();
|
||||||
|
if (jobId != null) {
|
||||||
|
log.debug(
|
||||||
|
"Recording retry attempt for job {} in TaskManager",
|
||||||
|
jobId);
|
||||||
|
// Retry info is tracked in TaskManager for REST API access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use non-blocking delay for all retry attempts to avoid blocking
|
||||||
|
// threads
|
||||||
|
// For sync jobs this avoids starving the tomcat thread pool under
|
||||||
|
// load
|
||||||
|
long delayMs = RETRY_BASE_DELAY.toMillis() * currentAttempt;
|
||||||
|
|
||||||
|
// Execute the retry after a delay through the JobExecutorService
|
||||||
|
// rather than blocking the current thread with sleep
|
||||||
|
CompletableFuture<Object> delayedRetry = new CompletableFuture<>();
|
||||||
|
|
||||||
|
// Use a delayed executor for non-blocking delay
|
||||||
|
CompletableFuture.delayedExecutor(delayMs, TimeUnit.MILLISECONDS)
|
||||||
|
.execute(
|
||||||
|
() -> {
|
||||||
|
// Continue the retry loop in the next iteration
|
||||||
|
// We can't return from here directly since
|
||||||
|
// we're in a Runnable
|
||||||
|
delayedRetry.complete(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the delay to complete before continuing
|
||||||
|
try {
|
||||||
|
delayedRetry.join();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No more retries, we'll throw the exception after the loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, all retries failed
|
||||||
|
if (lastException != null) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Job failed after "
|
||||||
|
+ maxRetries
|
||||||
|
+ " attempts: "
|
||||||
|
+ lastException.getMessage(),
|
||||||
|
lastException);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should never happen if lastException is properly tracked
|
||||||
|
throw new RuntimeException("Job failed but no exception was recorded");
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
queueable,
|
||||||
|
resourceWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates deep copies of arguments when needed to avoid mutating the original objects
|
||||||
|
* Particularly important for PDFFile objects that might be reused by Spring
|
||||||
|
*
|
||||||
|
* @param originalArgs The original arguments
|
||||||
|
* @param async Whether this is an async operation
|
||||||
|
* @return A new array with safely processed arguments
|
||||||
|
*/
|
||||||
|
private Object[] copyAndProcessArgs(Object[] originalArgs, boolean async) {
|
||||||
|
if (originalArgs == null || originalArgs.length == 0) {
|
||||||
|
return originalArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] processedArgs = new Object[originalArgs.length];
|
||||||
|
|
||||||
|
// Copy all arguments
|
||||||
|
for (int i = 0; i < originalArgs.length; i++) {
|
||||||
|
Object arg = originalArgs[i];
|
||||||
|
|
||||||
|
if (arg instanceof PDFFile pdfFile) {
|
||||||
|
// Create a copy of PDFFile to avoid mutating the original
|
||||||
|
// Using direct property access instead of reflection for better performance
|
||||||
|
PDFFile pdfFileCopy = new PDFFile();
|
||||||
|
pdfFileCopy.setFileId(pdfFile.getFileId());
|
||||||
|
pdfFileCopy.setFileInput(pdfFile.getFileInput());
|
||||||
|
|
||||||
|
// Case 1: fileId is provided but no fileInput
|
||||||
|
if (pdfFileCopy.getFileInput() == null && pdfFileCopy.getFileId() != null) {
|
||||||
|
try {
|
||||||
|
log.debug("Using fileId {} to get file content", pdfFileCopy.getFileId());
|
||||||
|
MultipartFile file = fileStorage.retrieveFile(pdfFileCopy.getFileId());
|
||||||
|
pdfFileCopy.setFileInput(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Failed to resolve file by ID: " + pdfFileCopy.getFileId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Case 2: For async requests, we need to make a copy of the MultipartFile
|
||||||
|
else if (async && pdfFileCopy.getFileInput() != null) {
|
||||||
|
try {
|
||||||
|
log.debug("Making persistent copy of uploaded file for async processing");
|
||||||
|
MultipartFile originalFile = pdfFileCopy.getFileInput();
|
||||||
|
String fileId = fileStorage.storeFile(originalFile);
|
||||||
|
|
||||||
|
// Store the fileId for later reference
|
||||||
|
pdfFileCopy.setFileId(fileId);
|
||||||
|
|
||||||
|
// Replace the original MultipartFile with our persistent copy
|
||||||
|
MultipartFile persistentFile = fileStorage.retrieveFile(fileId);
|
||||||
|
pdfFileCopy.setFileInput(persistentFile);
|
||||||
|
|
||||||
|
log.debug("Created persistent file copy with fileId: {}", fileId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Failed to create persistent copy of uploaded file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processedArgs[i] = pdfFileCopy;
|
||||||
|
} else {
|
||||||
|
// For non-PDFFile objects, just pass the original reference
|
||||||
|
// If other classes need copy-on-write, add them here
|
||||||
|
processedArgs[i] = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes arguments in-place for testing purposes This is similar to our original
|
||||||
|
* implementation before introducing copy-on-write It's only used in test environments to
|
||||||
|
* maintain test compatibility
|
||||||
|
*
|
||||||
|
* @param originalArgs The original arguments
|
||||||
|
* @param async Whether this is an async operation
|
||||||
|
* @return The original array with processed arguments
|
||||||
|
*/
|
||||||
|
private Object[] processArgsInPlace(Object[] originalArgs, boolean async) {
|
||||||
|
if (originalArgs == null || originalArgs.length == 0) {
|
||||||
|
return originalArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all arguments in-place
|
||||||
|
for (int i = 0; i < originalArgs.length; i++) {
|
||||||
|
Object arg = originalArgs[i];
|
||||||
|
|
||||||
|
if (arg instanceof PDFFile pdfFile) {
|
||||||
|
// Case 1: fileId is provided but no fileInput
|
||||||
|
if (pdfFile.getFileInput() == null && pdfFile.getFileId() != null) {
|
||||||
|
try {
|
||||||
|
log.debug("Using fileId {} to get file content", pdfFile.getFileId());
|
||||||
|
MultipartFile file = fileStorage.retrieveFile(pdfFile.getFileId());
|
||||||
|
pdfFile.setFileInput(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Failed to resolve file by ID: " + pdfFile.getFileId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Case 2: For async requests, we need to make a copy of the MultipartFile
|
||||||
|
else if (async && pdfFile.getFileInput() != null) {
|
||||||
|
try {
|
||||||
|
log.debug("Making persistent copy of uploaded file for async processing");
|
||||||
|
MultipartFile originalFile = pdfFile.getFileInput();
|
||||||
|
String fileId = fileStorage.storeFile(originalFile);
|
||||||
|
|
||||||
|
// Store the fileId for later reference
|
||||||
|
pdfFile.setFileId(fileId);
|
||||||
|
|
||||||
|
// Replace the original MultipartFile with our persistent copy
|
||||||
|
MultipartFile persistentFile = fileStorage.retrieveFile(fileId);
|
||||||
|
pdfFile.setFileInput(persistentFile);
|
||||||
|
|
||||||
|
log.debug("Created persistent file copy with fileId: {}", fileId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Failed to create persistent copy of uploaded file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJobIdFromContext() {
|
||||||
|
try {
|
||||||
|
return (String) request.getAttribute("jobId");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not retrieve job ID from context: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -15,6 +15,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
@ -22,20 +23,33 @@ import org.springframework.core.io.Resource;
|
|||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AppConfig {
|
public class AppConfig {
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private final Environment env;
|
@Getter
|
||||||
|
@Value("${baseUrl:http://localhost}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Value("${server.servlet.context-path:/}")
|
||||||
|
private String contextPath;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Value("${server.port:8080}")
|
||||||
|
private String serverPort;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
|
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
|
||||||
@ -133,10 +147,24 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
|
||||||
@Bean(name = "activeSecurity")
|
@Bean(name = "activeSecurity")
|
||||||
|
public boolean activeSecurity() {
|
||||||
|
String disableAdditionalFeatures = env.getProperty("DISABLE_ADDITIONAL_FEATURES");
|
||||||
|
|
||||||
|
if (disableAdditionalFeatures != null) {
|
||||||
|
// DISABLE_ADDITIONAL_FEATURES=true means security OFF, so return false
|
||||||
|
// DISABLE_ADDITIONAL_FEATURES=false means security ON, so return true
|
||||||
|
return !Boolean.parseBoolean(disableAdditionalFeatures);
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.getProperty("DOCKER_ENABLE_SECURITY", Boolean.class, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "missingActiveSecurity")
|
||||||
|
@ConditionalOnMissingClass(
|
||||||
|
"stirling.software.proprietary.security.configuration.SecurityConfiguration")
|
||||||
public boolean missingActiveSecurity() {
|
public boolean missingActiveSecurity() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "directoryFilter")
|
@Bean(name = "directoryFilter")
|
||||||
@ -198,9 +226,58 @@ public class AppConfig {
|
|||||||
return applicationProperties.getAutomaticallyGenerated().getUUID();
|
return applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplicationProperties.Security security() {
|
||||||
|
return applicationProperties.getSecurity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplicationProperties.Security.OAUTH2 oAuth2() {
|
||||||
|
return applicationProperties.getSecurity().getOauth2();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplicationProperties.Premium premium() {
|
||||||
|
return applicationProperties.getPremium();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplicationProperties.System system() {
|
||||||
|
return applicationProperties.getSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplicationProperties.Datasource datasource() {
|
||||||
|
return applicationProperties.getSystem().getDatasource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "runningProOrHigher")
|
||||||
|
@Profile("default")
|
||||||
|
public boolean runningProOrHigher() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "runningEE")
|
||||||
|
@Profile("default")
|
||||||
|
public boolean runningEnterprise() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "GoogleDriveEnabled")
|
||||||
|
@Profile("default")
|
||||||
|
public boolean googleDriveEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "license")
|
||||||
|
@Profile("default")
|
||||||
|
public String licenseType() {
|
||||||
|
return "NORMAL";
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "disablePixel")
|
@Bean(name = "disablePixel")
|
||||||
public boolean disablePixel() {
|
public boolean disablePixel() {
|
||||||
return Boolean.getBoolean(env.getProperty("DISABLE_PIXEL"));
|
return Boolean.parseBoolean(env.getProperty("DISABLE_PIXEL", "false"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "machineType")
|
@Bean(name = "machineType")
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -13,6 +13,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.YamlHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A naive, line-based approach to merging "settings.yml" with "settings.yml.template" while
|
* A naive, line-based approach to merging "settings.yml" with "settings.yml.template" while
|
||||||
* preserving exact whitespace, blank lines, and inline comments -- but we only rewrite the file if
|
* preserving exact whitespace, blank lines, and inline comments -- but we only rewrite the file if
|
||||||
@ -76,7 +78,7 @@ public class ConfigInitializer {
|
|||||||
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
||||||
if (Files.notExists(customSettingsPath)) {
|
if (Files.notExists(customSettingsPath)) {
|
||||||
Files.createFile(customSettingsPath);
|
Files.createFile(customSettingsPath);
|
||||||
log.info("Created custom_settings file: {}", customSettingsPath.toString());
|
log.info("Created custom_settings file: {}", customSettingsPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -13,7 +13,7 @@ import org.thymeleaf.templateresource.ITemplateResource;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.InputStreamTemplateResource;
|
import stirling.software.common.model.InputStreamTemplateResource;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
|
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@ -50,7 +50,6 @@ public class InstallationPathConfig {
|
|||||||
return Paths.get(
|
return Paths.get(
|
||||||
System.getenv("APPDATA"), // parent path
|
System.getenv("APPDATA"), // parent path
|
||||||
"Stirling-PDF")
|
"Stirling-PDF")
|
||||||
.toString()
|
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
} else if (os.contains("mac")) {
|
} else if (os.contains("mac")) {
|
||||||
return Paths.get(
|
return Paths.get(
|
||||||
@ -58,14 +57,12 @@ public class InstallationPathConfig {
|
|||||||
"Library",
|
"Library",
|
||||||
"Application Support",
|
"Application Support",
|
||||||
"Stirling-PDF")
|
"Stirling-PDF")
|
||||||
.toString()
|
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
} else {
|
} else {
|
||||||
return Paths.get(
|
return Paths.get(
|
||||||
System.getProperty("user.home"), // parent path
|
System.getProperty("user.home"), // parent path
|
||||||
".config",
|
".config",
|
||||||
"Stirling-PDF")
|
"Stirling-PDF")
|
||||||
.toString()
|
|
||||||
+ File.separator;
|
+ File.separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -9,9 +9,9 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Operations;
|
import stirling.software.common.model.ApplicationProperties.CustomPaths.Operations;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Pipeline;
|
import stirling.software.common.model.ApplicationProperties.CustomPaths.Pipeline;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.common.configuration;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||||
@ -12,8 +11,7 @@ import org.springframework.core.io.support.PropertySourceFactory;
|
|||||||
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
|
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
|
||||||
throws IOException {
|
|
||||||
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
||||||
factory.setResources(encodedResource.getResource());
|
factory.setResources(encodedResource.getResource());
|
||||||
Properties properties = factory.getObject();
|
Properties properties = factory.getObject();
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config.interfaces;
|
package stirling.software.common.configuration.interfaces;
|
||||||
|
|
||||||
public interface ShowAdminInterface {
|
public interface ShowAdminInterface {
|
||||||
default boolean getShowUpdateOnlyAdmins() {
|
default boolean getShowUpdateOnlyAdmins() {
|
@ -1,6 +1,4 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.common.model;
|
||||||
|
|
||||||
import static stirling.software.SPDF.utils.validation.Validator.*;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@ -17,7 +15,6 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
@ -26,6 +23,7 @@ import org.springframework.core.io.ClassPathResource;
|
|||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.support.EncodedResource;
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -33,21 +31,37 @@ import lombok.Setter;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.common.configuration.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
import stirling.software.common.configuration.YamlPropertySourceFactory;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.model.provider.GitHubProvider;
|
import stirling.software.common.model.oauth2.GitHubProvider;
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.common.model.oauth2.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.common.model.oauth2.KeycloakProvider;
|
||||||
import stirling.software.SPDF.model.provider.Provider;
|
import stirling.software.common.model.oauth2.Provider;
|
||||||
|
import stirling.software.common.util.ValidationUtils;
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConfigurationProperties(prefix = "")
|
|
||||||
@Data
|
@Data
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@ConfigurationProperties(prefix = "")
|
||||||
public class ApplicationProperties {
|
public class ApplicationProperties {
|
||||||
|
|
||||||
|
private Legal legal = new Legal();
|
||||||
|
private Security security = new Security();
|
||||||
|
private System system = new System();
|
||||||
|
private Ui ui = new Ui();
|
||||||
|
private Endpoints endpoints = new Endpoints();
|
||||||
|
private Metrics metrics = new Metrics();
|
||||||
|
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
||||||
|
|
||||||
|
private Mail mail = new Mail();
|
||||||
|
|
||||||
|
private Premium premium = new Premium();
|
||||||
|
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
||||||
|
private AutoPipeline autoPipeline = new AutoPipeline();
|
||||||
|
private ProcessExecutor processExecutor = new ProcessExecutor();
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PropertySource<?> dynamicYamlPropertySource(ConfigurableEnvironment environment)
|
public PropertySource<?> dynamicYamlPropertySource(ConfigurableEnvironment environment)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -74,21 +88,6 @@ public class ApplicationProperties {
|
|||||||
return propertySource;
|
return propertySource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Legal legal = new Legal();
|
|
||||||
private Security security = new Security();
|
|
||||||
private System system = new System();
|
|
||||||
private Ui ui = new Ui();
|
|
||||||
private Endpoints endpoints = new Endpoints();
|
|
||||||
private Metrics metrics = new Metrics();
|
|
||||||
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
|
||||||
|
|
||||||
private Mail mail = new Mail();
|
|
||||||
|
|
||||||
private Premium premium = new Premium();
|
|
||||||
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
|
||||||
private AutoPipeline autoPipeline = new AutoPipeline();
|
|
||||||
private ProcessExecutor processExecutor = new ProcessExecutor();
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class AutoPipeline {
|
public static class AutoPipeline {
|
||||||
private String outputFolder;
|
private String outputFolder;
|
||||||
@ -248,11 +247,11 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSettingsValid() {
|
public boolean isSettingsValid() {
|
||||||
return !isStringEmpty(this.getIssuer())
|
return !ValidationUtils.isStringEmpty(this.getIssuer())
|
||||||
&& !isStringEmpty(this.getClientId())
|
&& !ValidationUtils.isStringEmpty(this.getClientId())
|
||||||
&& !isStringEmpty(this.getClientSecret())
|
&& !ValidationUtils.isStringEmpty(this.getClientSecret())
|
||||||
&& !isCollectionEmpty(this.getScopes())
|
&& !ValidationUtils.isCollectionEmpty(this.getScopes())
|
||||||
&& !isStringEmpty(this.getUseAsUsername());
|
&& !ValidationUtils.isStringEmpty(this.getUseAsUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@ -443,6 +442,7 @@ public class ApplicationProperties {
|
|||||||
@Data
|
@Data
|
||||||
public static class ProFeatures {
|
public static class ProFeatures {
|
||||||
private boolean ssoAutoLogin;
|
private boolean ssoAutoLogin;
|
||||||
|
private boolean database;
|
||||||
private CustomMetadata customMetadata = new CustomMetadata();
|
private CustomMetadata customMetadata = new CustomMetadata();
|
||||||
private GoogleDrive googleDrive = new GoogleDrive();
|
private GoogleDrive googleDrive = new GoogleDrive();
|
||||||
|
|
||||||
@ -488,6 +488,14 @@ public class ApplicationProperties {
|
|||||||
@Data
|
@Data
|
||||||
public static class EnterpriseFeatures {
|
public static class EnterpriseFeatures {
|
||||||
private PersistentMetrics persistentMetrics = new PersistentMetrics();
|
private PersistentMetrics persistentMetrics = new PersistentMetrics();
|
||||||
|
private Audit audit = new Audit();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Audit {
|
||||||
|
private boolean enabled = true;
|
||||||
|
private int level = 2; // 0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE
|
||||||
|
private int retentionDays = 90;
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class PersistentMetrics {
|
public static class PersistentMetrics {
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.model;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.common.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.common.model;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.api;
|
package stirling.software.common.model.api;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.api;
|
package stirling.software.common.model.api;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@ -14,8 +14,12 @@ import lombok.NoArgsConstructor;
|
|||||||
public class PDFFile {
|
public class PDFFile {
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "The input PDF file",
|
description = "The input PDF file",
|
||||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
|
||||||
contentMediaType = "application/pdf",
|
contentMediaType = "application/pdf",
|
||||||
format = "binary")
|
format = "binary")
|
||||||
private MultipartFile fileInput;
|
private MultipartFile fileInput;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "File ID for server-side files (can be used instead of fileInput)",
|
||||||
|
example = "a1b2c3d4-5678-90ab-cdef-ghijklmnopqr")
|
||||||
|
private String fileId;
|
||||||
}
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package stirling.software.common.model.api.converters;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class EmlToPdfRequest extends PDFFile {
|
||||||
|
|
||||||
|
// fileInput is inherited from PDFFile
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Include email attachments in the PDF output",
|
||||||
|
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||||
|
example = "false")
|
||||||
|
private boolean includeAttachments = false;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Maximum attachment size in MB to include (default 10MB, range: 1-100)",
|
||||||
|
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||||
|
example = "10",
|
||||||
|
minimum = "1",
|
||||||
|
maximum = "100")
|
||||||
|
private int maxAttachmentSizeMB = 10;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Download HTML intermediate file instead of PDF",
|
||||||
|
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||||
|
example = "false")
|
||||||
|
private boolean downloadHtml = false;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Include CC and BCC recipients in header (if available)",
|
||||||
|
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||||
|
example = "true")
|
||||||
|
private boolean includeAllRecipients = true;
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package stirling.software.SPDF.model.api.converters;
|
package stirling.software.common.model.api.converters;
|
||||||
|
|
||||||
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 stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.api.misc;
|
package stirling.software.common.model.api.misc;
|
||||||
|
|
||||||
public enum HighContrastColorCombination {
|
public enum HighContrastColorCombination {
|
||||||
WHITE_TEXT_ON_BLACK,
|
WHITE_TEXT_ON_BLACK,
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.api.misc;
|
package stirling.software.common.model.api.misc;
|
||||||
|
|
||||||
public enum ReplaceAndInvert {
|
public enum ReplaceAndInvert {
|
||||||
HIGH_CONTRAST_COLOR,
|
HIGH_CONTRAST_COLOR,
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.api.security;
|
package stirling.software.common.model.api.security;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.common.model.enumeration;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.common.model.enumeration;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.common.model.exception;
|
||||||
|
|
||||||
|
public class UnsupportedClaimException extends RuntimeException {
|
||||||
|
public UnsupportedClaimException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.exception;
|
package stirling.software.common.model.exception;
|
||||||
|
|
||||||
public class UnsupportedProviderException extends Exception {
|
public class UnsupportedProviderException extends Exception {
|
||||||
public UnsupportedProviderException(String message) {
|
public UnsupportedProviderException(String message) {
|
@ -0,0 +1,15 @@
|
|||||||
|
package stirling.software.common.model.job;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JobProgress {
|
||||||
|
private String jobId;
|
||||||
|
private String status;
|
||||||
|
private int percentComplete;
|
||||||
|
private String message;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package stirling.software.common.model.job;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JobResponse<T> {
|
||||||
|
private boolean async;
|
||||||
|
private String jobId;
|
||||||
|
private T result;
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
package stirling.software.common.model.job;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/** Represents the result of a job execution. Used by the TaskManager to store job results. */
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JobResult {
|
||||||
|
|
||||||
|
/** The job ID */
|
||||||
|
private String jobId;
|
||||||
|
|
||||||
|
/** Flag indicating if the job is complete */
|
||||||
|
private boolean complete;
|
||||||
|
|
||||||
|
/** Error message if the job failed */
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
/** The file ID of the result file, if applicable */
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
/** Original file name, if applicable */
|
||||||
|
private String originalFileName;
|
||||||
|
|
||||||
|
/** MIME type of the result, if applicable */
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
|
/** Time when the job was created */
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/** Time when the job was completed */
|
||||||
|
private LocalDateTime completedAt;
|
||||||
|
|
||||||
|
/** The actual result object, if not a file */
|
||||||
|
private Object result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes attached to this job for tracking purposes. Uses CopyOnWriteArrayList for thread safety
|
||||||
|
* when notes are added concurrently.
|
||||||
|
*/
|
||||||
|
private final List<String> notes = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new JobResult with the given job ID
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return A new JobResult
|
||||||
|
*/
|
||||||
|
public static JobResult createNew(String jobId) {
|
||||||
|
return JobResult.builder()
|
||||||
|
.jobId(jobId)
|
||||||
|
.complete(false)
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this job as complete with a file result
|
||||||
|
*
|
||||||
|
* @param fileId The file ID of the result
|
||||||
|
* @param originalFileName The original file name
|
||||||
|
* @param contentType The content type of the file
|
||||||
|
*/
|
||||||
|
public void completeWithFile(String fileId, String originalFileName, String contentType) {
|
||||||
|
this.complete = true;
|
||||||
|
this.fileId = fileId;
|
||||||
|
this.originalFileName = originalFileName;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.completedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this job as complete with a general result
|
||||||
|
*
|
||||||
|
* @param result The result object
|
||||||
|
*/
|
||||||
|
public void completeWithResult(Object result) {
|
||||||
|
this.complete = true;
|
||||||
|
this.result = result;
|
||||||
|
this.completedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this job as failed with an error message
|
||||||
|
*
|
||||||
|
* @param error The error message
|
||||||
|
*/
|
||||||
|
public void failWithError(String error) {
|
||||||
|
this.complete = true;
|
||||||
|
this.error = error;
|
||||||
|
this.completedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a note to this job
|
||||||
|
*
|
||||||
|
* @param note The note to add
|
||||||
|
*/
|
||||||
|
public void addNote(String note) {
|
||||||
|
this.notes.add(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all notes attached to this job
|
||||||
|
*
|
||||||
|
* @return An unmodifiable view of the notes list
|
||||||
|
*/
|
||||||
|
public List<String> getNotes() {
|
||||||
|
return Collections.unmodifiableList(notes);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package stirling.software.common.model.job;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/** Represents statistics about jobs in the system */
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JobStats {
|
||||||
|
|
||||||
|
/** Total number of jobs (active and completed) */
|
||||||
|
private int totalJobs;
|
||||||
|
|
||||||
|
/** Number of active (incomplete) jobs */
|
||||||
|
private int activeJobs;
|
||||||
|
|
||||||
|
/** Number of completed jobs */
|
||||||
|
private int completedJobs;
|
||||||
|
|
||||||
|
/** Number of failed jobs */
|
||||||
|
private int failedJobs;
|
||||||
|
|
||||||
|
/** Number of successful jobs */
|
||||||
|
private int successfulJobs;
|
||||||
|
|
||||||
|
/** Number of jobs with file results */
|
||||||
|
private int fileResultJobs;
|
||||||
|
|
||||||
|
/** The oldest active job's creation timestamp */
|
||||||
|
private LocalDateTime oldestActiveJobTime;
|
||||||
|
|
||||||
|
/** The newest active job's creation timestamp */
|
||||||
|
private LocalDateTime newestActiveJobTime;
|
||||||
|
|
||||||
|
/** The average processing time for completed jobs in milliseconds */
|
||||||
|
private long averageProcessingTimeMs;
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
package stirling.software.common.model.oauth2;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.common.model.enumeration.UsernameAttribute;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class GitHubProvider extends Provider {
|
public class GitHubProvider extends Provider {
|
@ -1,11 +1,11 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
package stirling.software.common.model.oauth2;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.common.model.enumeration.UsernameAttribute;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class GoogleProvider extends Provider {
|
public class GoogleProvider extends Provider {
|
@ -1,11 +1,11 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
package stirling.software.common.model.oauth2;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.common.model.enumeration.UsernameAttribute;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class KeycloakProvider extends Provider {
|
public class KeycloakProvider extends Provider {
|
@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
package stirling.software.common.model.oauth2;
|
||||||
|
|
||||||
import static stirling.software.SPDF.model.UsernameAttribute.EMAIL;
|
import static stirling.software.common.model.enumeration.UsernameAttribute.EMAIL;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -9,8 +9,8 @@ import java.util.Collection;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.common.model.enumeration.UsernameAttribute;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedUsernameAttribute;
|
import stirling.software.common.model.exception.UnsupportedClaimException;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@ -83,7 +83,7 @@ public class Provider {
|
|||||||
return usernameAttribute;
|
return usernameAttribute;
|
||||||
}
|
}
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedUsernameAttribute(
|
throw new UnsupportedClaimException(
|
||||||
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ public class Provider {
|
|||||||
return usernameAttribute;
|
return usernameAttribute;
|
||||||
}
|
}
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedUsernameAttribute(
|
throw new UnsupportedClaimException(
|
||||||
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ public class Provider {
|
|||||||
return usernameAttribute;
|
return usernameAttribute;
|
||||||
}
|
}
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedUsernameAttribute(
|
throw new UnsupportedClaimException(
|
||||||
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.common.service;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -22,7 +22,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adaptive PDF document factory that optimizes memory usage based on file size and available system
|
* Adaptive PDF document factory that optimizes memory usage based on file size and available system
|
@ -0,0 +1,78 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FileOrUploadService {
|
||||||
|
|
||||||
|
@Value("${stirling.tempDir:/tmp/stirling-files}")
|
||||||
|
private String tempDirPath;
|
||||||
|
|
||||||
|
public Path resolveFilePath(String fileId) {
|
||||||
|
return Path.of(tempDirPath).resolve(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipartFile toMockMultipartFile(String name, byte[] data) throws IOException {
|
||||||
|
return new CustomMultipartFile(name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom implementation of MultipartFile
|
||||||
|
private static class CustomMultipartFile implements MultipartFile {
|
||||||
|
private final String name;
|
||||||
|
private final byte[] content;
|
||||||
|
|
||||||
|
public CustomMultipartFile(String name, byte[] content) {
|
||||||
|
this.name = name;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return "application/pdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return content == null || content.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
return content.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes() throws IOException {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.io.InputStream getInputStream() throws IOException {
|
||||||
|
return new ByteArrayInputStream(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
|
||||||
|
Files.write(dest.toPath(), content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for storing and retrieving files with unique file IDs. Used by the AutoJobPostMapping
|
||||||
|
* system to handle file references.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class FileStorage {
|
||||||
|
|
||||||
|
@Value("${stirling.tempDir:/tmp/stirling-files}")
|
||||||
|
private String tempDirPath;
|
||||||
|
|
||||||
|
private final FileOrUploadService fileOrUploadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a file and return its unique ID
|
||||||
|
*
|
||||||
|
* @param file The file to store
|
||||||
|
* @return The unique ID assigned to the file
|
||||||
|
* @throws IOException If there is an error storing the file
|
||||||
|
*/
|
||||||
|
public String storeFile(MultipartFile file) throws IOException {
|
||||||
|
String fileId = generateFileId();
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
Files.createDirectories(filePath.getParent());
|
||||||
|
|
||||||
|
// Transfer the file to the storage location
|
||||||
|
file.transferTo(filePath.toFile());
|
||||||
|
|
||||||
|
log.debug("Stored file with ID: {}", fileId);
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a byte array as a file and return its unique ID
|
||||||
|
*
|
||||||
|
* @param bytes The byte array to store
|
||||||
|
* @param originalName The original name of the file (for extension)
|
||||||
|
* @return The unique ID assigned to the file
|
||||||
|
* @throws IOException If there is an error storing the file
|
||||||
|
*/
|
||||||
|
public String storeBytes(byte[] bytes, String originalName) throws IOException {
|
||||||
|
String fileId = generateFileId();
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
Files.createDirectories(filePath.getParent());
|
||||||
|
|
||||||
|
// Write the bytes to the file
|
||||||
|
Files.write(filePath, bytes);
|
||||||
|
|
||||||
|
log.debug("Stored byte array with ID: {}", fileId);
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a file by its ID as a MultipartFile
|
||||||
|
*
|
||||||
|
* @param fileId The ID of the file to retrieve
|
||||||
|
* @return The file as a MultipartFile
|
||||||
|
* @throws IOException If the file doesn't exist or can't be read
|
||||||
|
*/
|
||||||
|
public MultipartFile retrieveFile(String fileId) throws IOException {
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
|
||||||
|
if (!Files.exists(filePath)) {
|
||||||
|
throw new IOException("File not found with ID: " + fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fileData = Files.readAllBytes(filePath);
|
||||||
|
return fileOrUploadService.toMockMultipartFile(fileId, fileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a file by its ID as a byte array
|
||||||
|
*
|
||||||
|
* @param fileId The ID of the file to retrieve
|
||||||
|
* @return The file as a byte array
|
||||||
|
* @throws IOException If the file doesn't exist or can't be read
|
||||||
|
*/
|
||||||
|
public byte[] retrieveBytes(String fileId) throws IOException {
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
|
||||||
|
if (!Files.exists(filePath)) {
|
||||||
|
throw new IOException("File not found with ID: " + fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Files.readAllBytes(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a file by its ID
|
||||||
|
*
|
||||||
|
* @param fileId The ID of the file to delete
|
||||||
|
* @return true if the file was deleted, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean deleteFile(String fileId) {
|
||||||
|
try {
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
return Files.deleteIfExists(filePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error deleting file with ID: {}", fileId, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file exists by its ID
|
||||||
|
*
|
||||||
|
* @param fileId The ID of the file to check
|
||||||
|
* @return true if the file exists, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean fileExists(String fileId) {
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
return Files.exists(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path for a file ID
|
||||||
|
*
|
||||||
|
* @param fileId The ID of the file
|
||||||
|
* @return The path to the file
|
||||||
|
*/
|
||||||
|
private Path getFilePath(String fileId) {
|
||||||
|
return Path.of(tempDirPath).resolve(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique file ID
|
||||||
|
*
|
||||||
|
* @return A unique file ID
|
||||||
|
*/
|
||||||
|
private String generateFileId() {
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,476 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.model.job.JobResponse;
|
||||||
|
import stirling.software.common.util.ExecutorFactory;
|
||||||
|
|
||||||
|
/** Service for executing jobs asynchronously or synchronously */
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class JobExecutorService {
|
||||||
|
|
||||||
|
private final TaskManager taskManager;
|
||||||
|
private final FileStorage fileStorage;
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
private final ResourceMonitor resourceMonitor;
|
||||||
|
private final JobQueue jobQueue;
|
||||||
|
private final ExecutorService executor = ExecutorFactory.newVirtualOrCachedThreadExecutor();
|
||||||
|
private final long effectiveTimeoutMs;
|
||||||
|
|
||||||
|
public JobExecutorService(
|
||||||
|
TaskManager taskManager,
|
||||||
|
FileStorage fileStorage,
|
||||||
|
HttpServletRequest request,
|
||||||
|
ResourceMonitor resourceMonitor,
|
||||||
|
JobQueue jobQueue,
|
||||||
|
@Value("${spring.mvc.async.request-timeout:1200000}") long asyncRequestTimeoutMs,
|
||||||
|
@Value("${server.servlet.session.timeout:30m}") String sessionTimeout) {
|
||||||
|
this.taskManager = taskManager;
|
||||||
|
this.fileStorage = fileStorage;
|
||||||
|
this.request = request;
|
||||||
|
this.resourceMonitor = resourceMonitor;
|
||||||
|
this.jobQueue = jobQueue;
|
||||||
|
|
||||||
|
// Parse session timeout and calculate effective timeout once during initialization
|
||||||
|
long sessionTimeoutMs = parseSessionTimeout(sessionTimeout);
|
||||||
|
this.effectiveTimeoutMs = Math.min(asyncRequestTimeoutMs, sessionTimeoutMs);
|
||||||
|
log.debug(
|
||||||
|
"Job executor configured with effective timeout of {} ms", this.effectiveTimeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a job either asynchronously or synchronously
|
||||||
|
*
|
||||||
|
* @param async Whether to run the job asynchronously
|
||||||
|
* @param work The work to be done
|
||||||
|
* @return The response
|
||||||
|
*/
|
||||||
|
public ResponseEntity<?> runJobGeneric(boolean async, Supplier<Object> work) {
|
||||||
|
return runJobGeneric(async, work, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a job either asynchronously or synchronously with a custom timeout
|
||||||
|
*
|
||||||
|
* @param async Whether to run the job asynchronously
|
||||||
|
* @param work The work to be done
|
||||||
|
* @param customTimeoutMs Custom timeout in milliseconds, or -1 to use the default
|
||||||
|
* @return The response
|
||||||
|
*/
|
||||||
|
public ResponseEntity<?> runJobGeneric(
|
||||||
|
boolean async, Supplier<Object> work, long customTimeoutMs) {
|
||||||
|
return runJobGeneric(async, work, customTimeoutMs, false, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a job either asynchronously or synchronously with custom parameters
|
||||||
|
*
|
||||||
|
* @param async Whether to run the job asynchronously
|
||||||
|
* @param work The work to be done
|
||||||
|
* @param customTimeoutMs Custom timeout in milliseconds, or -1 to use the default
|
||||||
|
* @param queueable Whether this job can be queued when system resources are limited
|
||||||
|
* @param resourceWeight The resource weight of this job (1-100)
|
||||||
|
* @return The response
|
||||||
|
*/
|
||||||
|
public ResponseEntity<?> runJobGeneric(
|
||||||
|
boolean async,
|
||||||
|
Supplier<Object> work,
|
||||||
|
long customTimeoutMs,
|
||||||
|
boolean queueable,
|
||||||
|
int resourceWeight) {
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
// Store the job ID in the request for potential use by other components
|
||||||
|
if (request != null) {
|
||||||
|
request.setAttribute("jobId", jobId);
|
||||||
|
|
||||||
|
// Also track this job ID in the user's session for authorization purposes
|
||||||
|
// This ensures users can only cancel their own jobs
|
||||||
|
if (request.getSession() != null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.Set<String> userJobIds =
|
||||||
|
(java.util.Set<String>) request.getSession().getAttribute("userJobIds");
|
||||||
|
|
||||||
|
if (userJobIds == null) {
|
||||||
|
userJobIds = new java.util.concurrent.ConcurrentSkipListSet<>();
|
||||||
|
request.getSession().setAttribute("userJobIds", userJobIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
userJobIds.add(jobId);
|
||||||
|
log.debug("Added job ID {} to user session", jobId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which timeout to use
|
||||||
|
long timeoutToUse = customTimeoutMs > 0 ? customTimeoutMs : effectiveTimeoutMs;
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Running job with ID: {}, async: {}, timeout: {}ms, queueable: {}, weight: {}",
|
||||||
|
jobId,
|
||||||
|
async,
|
||||||
|
timeoutToUse,
|
||||||
|
queueable,
|
||||||
|
resourceWeight);
|
||||||
|
|
||||||
|
// Check if we need to queue this job based on resource availability
|
||||||
|
boolean shouldQueue =
|
||||||
|
queueable
|
||||||
|
&& async
|
||||||
|
&& // Only async jobs can be queued
|
||||||
|
resourceMonitor.shouldQueueJob(resourceWeight);
|
||||||
|
|
||||||
|
if (shouldQueue) {
|
||||||
|
// Queue the job instead of executing immediately
|
||||||
|
log.debug(
|
||||||
|
"Queueing job {} due to resource constraints (weight: {})",
|
||||||
|
jobId,
|
||||||
|
resourceWeight);
|
||||||
|
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
|
||||||
|
// Create a specialized wrapper that updates the TaskManager
|
||||||
|
Supplier<Object> wrappedWork =
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
Object result = work.get();
|
||||||
|
processJobResult(jobId, result);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(
|
||||||
|
"Error executing queued job {}: {}", jobId, e.getMessage(), e);
|
||||||
|
taskManager.setError(jobId, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Queue the job and get the future
|
||||||
|
CompletableFuture<ResponseEntity<?>> future =
|
||||||
|
jobQueue.queueJob(jobId, resourceWeight, wrappedWork, timeoutToUse);
|
||||||
|
|
||||||
|
// Return immediately with job ID
|
||||||
|
return ResponseEntity.ok().body(new JobResponse<>(true, jobId, null));
|
||||||
|
} else if (async) {
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
executor.execute(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
log.debug(
|
||||||
|
"Running async job {} with timeout {} ms", jobId, timeoutToUse);
|
||||||
|
|
||||||
|
// Execute with timeout
|
||||||
|
Object result = executeWithTimeout(() -> work.get(), timeoutToUse);
|
||||||
|
processJobResult(jobId, result);
|
||||||
|
} catch (TimeoutException te) {
|
||||||
|
log.error("Job {} timed out after {} ms", jobId, timeoutToUse);
|
||||||
|
taskManager.setError(jobId, "Job timed out");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
|
||||||
|
taskManager.setError(jobId, e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new JobResponse<>(true, jobId, null));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
log.debug("Running sync job with timeout {} ms", timeoutToUse);
|
||||||
|
|
||||||
|
// Execute with timeout
|
||||||
|
Object result = executeWithTimeout(() -> work.get(), timeoutToUse);
|
||||||
|
|
||||||
|
// If the result is already a ResponseEntity, return it directly
|
||||||
|
if (result instanceof ResponseEntity) {
|
||||||
|
return (ResponseEntity<?>) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process different result types
|
||||||
|
return handleResultForSyncJob(result);
|
||||||
|
} catch (TimeoutException te) {
|
||||||
|
log.error("Synchronous job timed out after {} ms", timeoutToUse);
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body(Map.of("error", "Job timed out after " + timeoutToUse + " ms"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error executing synchronous job: {}", e.getMessage(), e);
|
||||||
|
// Construct a JSON error response
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body(Map.of("error", "Job failed: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the result of an asynchronous job
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param result The result
|
||||||
|
*/
|
||||||
|
private void processJobResult(String jobId, Object result) {
|
||||||
|
try {
|
||||||
|
if (result instanceof byte[]) {
|
||||||
|
// Store byte array directly to disk to avoid double memory consumption
|
||||||
|
String fileId = fileStorage.storeBytes((byte[]) result, "result.pdf");
|
||||||
|
taskManager.setFileResult(jobId, fileId, "result.pdf", "application/pdf");
|
||||||
|
log.debug("Stored byte[] result with fileId: {}", fileId);
|
||||||
|
|
||||||
|
// Let the byte array get collected naturally in the next GC cycle
|
||||||
|
// We don't need to force System.gc() which can be harmful
|
||||||
|
} else if (result instanceof ResponseEntity) {
|
||||||
|
ResponseEntity<?> response = (ResponseEntity<?>) result;
|
||||||
|
Object body = response.getBody();
|
||||||
|
|
||||||
|
if (body instanceof byte[]) {
|
||||||
|
// Extract filename from content-disposition header if available
|
||||||
|
String filename = "result.pdf";
|
||||||
|
String contentType = "application/pdf";
|
||||||
|
|
||||||
|
if (response.getHeaders().getContentDisposition() != null) {
|
||||||
|
String disposition =
|
||||||
|
response.getHeaders().getContentDisposition().toString();
|
||||||
|
if (disposition.contains("filename=")) {
|
||||||
|
filename =
|
||||||
|
disposition.substring(
|
||||||
|
disposition.indexOf("filename=") + 9,
|
||||||
|
disposition.lastIndexOf("\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getHeaders().getContentType() != null) {
|
||||||
|
contentType = response.getHeaders().getContentType().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store byte array directly to disk
|
||||||
|
String fileId = fileStorage.storeBytes((byte[]) body, filename);
|
||||||
|
taskManager.setFileResult(jobId, fileId, filename, contentType);
|
||||||
|
log.debug("Stored ResponseEntity<byte[]> result with fileId: {}", fileId);
|
||||||
|
|
||||||
|
// Let the GC handle the memory naturally
|
||||||
|
} else {
|
||||||
|
// Check if the response body contains a fileId
|
||||||
|
if (body != null && body.toString().contains("fileId")) {
|
||||||
|
try {
|
||||||
|
// Try to extract fileId using reflection
|
||||||
|
java.lang.reflect.Method getFileId =
|
||||||
|
body.getClass().getMethod("getFileId");
|
||||||
|
String fileId = (String) getFileId.invoke(body);
|
||||||
|
|
||||||
|
if (fileId != null && !fileId.isEmpty()) {
|
||||||
|
// Try to get filename and content type
|
||||||
|
String filename = "result.pdf";
|
||||||
|
String contentType = "application/pdf";
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method getOriginalFileName =
|
||||||
|
body.getClass().getMethod("getOriginalFilename");
|
||||||
|
String origName = (String) getOriginalFileName.invoke(body);
|
||||||
|
if (origName != null && !origName.isEmpty()) {
|
||||||
|
filename = origName;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug(
|
||||||
|
"Could not get original filename: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method getContentType =
|
||||||
|
body.getClass().getMethod("getContentType");
|
||||||
|
String ct = (String) getContentType.invoke(body);
|
||||||
|
if (ct != null && !ct.isEmpty()) {
|
||||||
|
contentType = ct;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not get content type: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
taskManager.setFileResult(jobId, fileId, filename, contentType);
|
||||||
|
log.debug("Extracted fileId from response body: {}", fileId);
|
||||||
|
|
||||||
|
taskManager.setComplete(jobId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug(
|
||||||
|
"Failed to extract fileId from response body: {}",
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store generic result
|
||||||
|
taskManager.setResult(jobId, body);
|
||||||
|
}
|
||||||
|
} else if (result instanceof MultipartFile) {
|
||||||
|
MultipartFile file = (MultipartFile) result;
|
||||||
|
String fileId = fileStorage.storeFile(file);
|
||||||
|
taskManager.setFileResult(
|
||||||
|
jobId, fileId, file.getOriginalFilename(), file.getContentType());
|
||||||
|
log.debug("Stored MultipartFile result with fileId: {}", fileId);
|
||||||
|
} else {
|
||||||
|
// Check if result has a fileId field
|
||||||
|
if (result != null) {
|
||||||
|
try {
|
||||||
|
// Try to extract fileId using reflection
|
||||||
|
java.lang.reflect.Method getFileId =
|
||||||
|
result.getClass().getMethod("getFileId");
|
||||||
|
String fileId = (String) getFileId.invoke(result);
|
||||||
|
|
||||||
|
if (fileId != null && !fileId.isEmpty()) {
|
||||||
|
// Try to get filename and content type
|
||||||
|
String filename = "result.pdf";
|
||||||
|
String contentType = "application/pdf";
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method getOriginalFileName =
|
||||||
|
result.getClass().getMethod("getOriginalFilename");
|
||||||
|
String origName = (String) getOriginalFileName.invoke(result);
|
||||||
|
if (origName != null && !origName.isEmpty()) {
|
||||||
|
filename = origName;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not get original filename: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method getContentType =
|
||||||
|
result.getClass().getMethod("getContentType");
|
||||||
|
String ct = (String) getContentType.invoke(result);
|
||||||
|
if (ct != null && !ct.isEmpty()) {
|
||||||
|
contentType = ct;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not get content type: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
taskManager.setFileResult(jobId, fileId, filename, contentType);
|
||||||
|
log.debug("Extracted fileId from result object: {}", fileId);
|
||||||
|
|
||||||
|
taskManager.setComplete(jobId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug(
|
||||||
|
"Failed to extract fileId from result object: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default case: store the result as is
|
||||||
|
taskManager.setResult(jobId, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
taskManager.setComplete(jobId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error processing job result: {}", e.getMessage(), e);
|
||||||
|
taskManager.setError(jobId, "Error processing result: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle different result types for synchronous jobs
|
||||||
|
*
|
||||||
|
* @param result The result object
|
||||||
|
* @return The appropriate ResponseEntity
|
||||||
|
* @throws IOException If there is an error processing the result
|
||||||
|
*/
|
||||||
|
private ResponseEntity<?> handleResultForSyncJob(Object result) throws IOException {
|
||||||
|
if (result instanceof byte[]) {
|
||||||
|
// Return byte array as PDF
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_PDF)
|
||||||
|
.header(
|
||||||
|
HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"form-data; name=\"attachment\"; filename=\"result.pdf\"")
|
||||||
|
.body(result);
|
||||||
|
} else if (result instanceof MultipartFile) {
|
||||||
|
// Return MultipartFile content
|
||||||
|
MultipartFile file = (MultipartFile) result;
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(file.getContentType()))
|
||||||
|
.header(
|
||||||
|
HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"form-data; name=\"attachment\"; filename=\""
|
||||||
|
+ file.getOriginalFilename()
|
||||||
|
+ "\"")
|
||||||
|
.body(file.getBytes());
|
||||||
|
} else {
|
||||||
|
// Default case: return as JSON
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse session timeout string (e.g., "30m", "1h") to milliseconds
|
||||||
|
*
|
||||||
|
* @param timeout The timeout string
|
||||||
|
* @return The timeout in milliseconds
|
||||||
|
*/
|
||||||
|
private long parseSessionTimeout(String timeout) {
|
||||||
|
if (timeout == null || timeout.isEmpty()) {
|
||||||
|
return 30 * 60 * 1000; // Default: 30 minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String value = timeout.replaceAll("[^\\d.]", "");
|
||||||
|
String unit = timeout.replaceAll("[\\d.]", "");
|
||||||
|
|
||||||
|
double numericValue = Double.parseDouble(value);
|
||||||
|
|
||||||
|
return switch (unit.toLowerCase()) {
|
||||||
|
case "s" -> (long) (numericValue * 1000);
|
||||||
|
case "m" -> (long) (numericValue * 60 * 1000);
|
||||||
|
case "h" -> (long) (numericValue * 60 * 60 * 1000);
|
||||||
|
case "d" -> (long) (numericValue * 24 * 60 * 60 * 1000);
|
||||||
|
default -> (long) (numericValue * 60 * 1000); // Default to minutes
|
||||||
|
};
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Could not parse session timeout '{}', using default", timeout);
|
||||||
|
return 30 * 60 * 1000; // Default: 30 minutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a supplier with a timeout
|
||||||
|
*
|
||||||
|
* @param supplier The supplier to execute
|
||||||
|
* @param timeoutMs The timeout in milliseconds
|
||||||
|
* @return The result from the supplier
|
||||||
|
* @throws TimeoutException If the execution times out
|
||||||
|
* @throws Exception If the supplier throws an exception
|
||||||
|
*/
|
||||||
|
private <T> T executeWithTimeout(Supplier<T> supplier, long timeoutMs)
|
||||||
|
throws TimeoutException, Exception {
|
||||||
|
// Use the same executor as other async jobs for consistency
|
||||||
|
// This ensures all operations run on the same thread pool
|
||||||
|
java.util.concurrent.CompletableFuture<T> future =
|
||||||
|
java.util.concurrent.CompletableFuture.supplyAsync(supplier, executor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (java.util.concurrent.TimeoutException e) {
|
||||||
|
future.cancel(true);
|
||||||
|
throw new TimeoutException("Execution timed out after " + timeoutMs + " ms");
|
||||||
|
} catch (java.util.concurrent.ExecutionException e) {
|
||||||
|
throw (Exception) e.getCause();
|
||||||
|
} catch (java.util.concurrent.CancellationException e) {
|
||||||
|
throw new Exception("Execution was cancelled", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new Exception("Execution was interrupted", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,495 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.SmartLifecycle;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.ExecutorFactory;
|
||||||
|
import stirling.software.common.util.SpringContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a queue of jobs with dynamic sizing based on system resources. Used when system resources
|
||||||
|
* are limited to prevent overloading.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class JobQueue implements SmartLifecycle {
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
private final ResourceMonitor resourceMonitor;
|
||||||
|
|
||||||
|
@Value("${stirling.job.queue.base-capacity:10}")
|
||||||
|
private int baseQueueCapacity = 10;
|
||||||
|
|
||||||
|
@Value("${stirling.job.queue.min-capacity:2}")
|
||||||
|
private int minQueueCapacity = 2;
|
||||||
|
|
||||||
|
@Value("${stirling.job.queue.check-interval-ms:1000}")
|
||||||
|
private long queueCheckIntervalMs = 1000;
|
||||||
|
|
||||||
|
@Value("${stirling.job.queue.max-wait-time-ms:600000}")
|
||||||
|
private long maxWaitTimeMs = 600000; // 10 minutes
|
||||||
|
|
||||||
|
private volatile BlockingQueue<QueuedJob> jobQueue;
|
||||||
|
private final Map<String, QueuedJob> jobMap = new ConcurrentHashMap<>();
|
||||||
|
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private final ExecutorService jobExecutor = ExecutorFactory.newVirtualOrCachedThreadExecutor();
|
||||||
|
private final Object queueLock = new Object(); // Lock for synchronizing queue operations
|
||||||
|
|
||||||
|
private boolean shuttingDown = false;
|
||||||
|
|
||||||
|
@Getter private int rejectedJobs = 0;
|
||||||
|
|
||||||
|
@Getter private int totalQueuedJobs = 0;
|
||||||
|
|
||||||
|
@Getter private int currentQueueSize = 0;
|
||||||
|
|
||||||
|
/** Represents a job waiting in the queue. */
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
private static class QueuedJob {
|
||||||
|
private final String jobId;
|
||||||
|
private final int resourceWeight;
|
||||||
|
private final Supplier<Object> work;
|
||||||
|
private final long timeoutMs;
|
||||||
|
private final Instant queuedAt;
|
||||||
|
private CompletableFuture<ResponseEntity<?>> future;
|
||||||
|
private volatile boolean cancelled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobQueue(ResourceMonitor resourceMonitor) {
|
||||||
|
this.resourceMonitor = resourceMonitor;
|
||||||
|
|
||||||
|
// Initialize with dynamic capacity
|
||||||
|
int capacity =
|
||||||
|
resourceMonitor.calculateDynamicQueueCapacity(baseQueueCapacity, minQueueCapacity);
|
||||||
|
this.jobQueue = new LinkedBlockingQueue<>(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove @PostConstruct to let SmartLifecycle control startup
|
||||||
|
private void initializeSchedulers() {
|
||||||
|
log.debug(
|
||||||
|
"Starting job queue with base capacity {}, min capacity {}",
|
||||||
|
baseQueueCapacity,
|
||||||
|
minQueueCapacity);
|
||||||
|
|
||||||
|
// Periodically process the job queue
|
||||||
|
scheduler.scheduleWithFixedDelay(
|
||||||
|
this::processQueue, 0, queueCheckIntervalMs, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// Periodically update queue capacity based on resource usage
|
||||||
|
scheduler.scheduleWithFixedDelay(
|
||||||
|
this::updateQueueCapacity,
|
||||||
|
10000, // Initial delay
|
||||||
|
30000, // 30 second interval
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove @PreDestroy to let SmartLifecycle control shutdown
|
||||||
|
private void shutdownSchedulers() {
|
||||||
|
log.info("Shutting down job queue");
|
||||||
|
shuttingDown = true;
|
||||||
|
|
||||||
|
// Complete any futures that are still waiting
|
||||||
|
jobMap.forEach(
|
||||||
|
(id, job) -> {
|
||||||
|
if (!job.future.isDone()) {
|
||||||
|
job.future.completeExceptionally(
|
||||||
|
new RuntimeException("Server shutting down, job cancelled"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shutdown schedulers and wait for termination
|
||||||
|
try {
|
||||||
|
scheduler.shutdown();
|
||||||
|
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
jobExecutor.shutdown();
|
||||||
|
if (!jobExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
jobExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
jobExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"Job queue shutdown complete. Stats: total={}, rejected={}",
|
||||||
|
totalQueuedJobs,
|
||||||
|
rejectedJobs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmartLifecycle methods
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
log.info("Starting JobQueue lifecycle");
|
||||||
|
if (!running) {
|
||||||
|
initializeSchedulers();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
log.info("Stopping JobQueue lifecycle");
|
||||||
|
shutdownSchedulers();
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPhase() {
|
||||||
|
// Start earlier than most components, but shutdown later
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAutoStartup() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues a job for execution when resources permit.
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param resourceWeight The resource weight of the job (1-100)
|
||||||
|
* @param work The work to be done
|
||||||
|
* @param timeoutMs The timeout in milliseconds
|
||||||
|
* @return A CompletableFuture that will complete when the job is executed
|
||||||
|
*/
|
||||||
|
public CompletableFuture<ResponseEntity<?>> queueJob(
|
||||||
|
String jobId, int resourceWeight, Supplier<Object> work, long timeoutMs) {
|
||||||
|
|
||||||
|
// Create a CompletableFuture to track this job's completion
|
||||||
|
CompletableFuture<ResponseEntity<?>> future = new CompletableFuture<>();
|
||||||
|
|
||||||
|
// Create the queued job
|
||||||
|
QueuedJob job =
|
||||||
|
new QueuedJob(jobId, resourceWeight, work, timeoutMs, Instant.now(), future, false);
|
||||||
|
|
||||||
|
// Store in our map for lookup
|
||||||
|
jobMap.put(jobId, job);
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
totalQueuedJobs++;
|
||||||
|
|
||||||
|
// Synchronize access to the queue
|
||||||
|
synchronized (queueLock) {
|
||||||
|
currentQueueSize = jobQueue.size();
|
||||||
|
|
||||||
|
// Try to add to the queue
|
||||||
|
try {
|
||||||
|
boolean added = jobQueue.offer(job, 5, TimeUnit.SECONDS);
|
||||||
|
if (!added) {
|
||||||
|
log.warn("Queue full, rejecting job {}", jobId);
|
||||||
|
rejectedJobs++;
|
||||||
|
future.completeExceptionally(
|
||||||
|
new RuntimeException("Job queue full, please try again later"));
|
||||||
|
jobMap.remove(jobId);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Job {} queued for execution (weight: {}, queue size: {})",
|
||||||
|
jobId,
|
||||||
|
resourceWeight,
|
||||||
|
jobQueue.size());
|
||||||
|
|
||||||
|
return future;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
future.completeExceptionally(new RuntimeException("Job queue interrupted"));
|
||||||
|
jobMap.remove(jobId);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current capacity of the job queue.
|
||||||
|
*
|
||||||
|
* @return The current capacity
|
||||||
|
*/
|
||||||
|
public int getQueueCapacity() {
|
||||||
|
synchronized (queueLock) {
|
||||||
|
return ((LinkedBlockingQueue<QueuedJob>) jobQueue).remainingCapacity()
|
||||||
|
+ jobQueue.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the capacity of the job queue based on available system resources. */
|
||||||
|
private void updateQueueCapacity() {
|
||||||
|
try {
|
||||||
|
// Calculate new capacity once and cache the result
|
||||||
|
int newCapacity =
|
||||||
|
resourceMonitor.calculateDynamicQueueCapacity(
|
||||||
|
baseQueueCapacity, minQueueCapacity);
|
||||||
|
|
||||||
|
int currentCapacity = getQueueCapacity();
|
||||||
|
if (newCapacity != currentCapacity) {
|
||||||
|
log.debug(
|
||||||
|
"Updating job queue capacity from {} to {}", currentCapacity, newCapacity);
|
||||||
|
|
||||||
|
synchronized (queueLock) {
|
||||||
|
// Double-check that capacity still needs to be updated
|
||||||
|
// Use the cached currentCapacity to avoid calling getQueueCapacity() again
|
||||||
|
if (newCapacity != currentCapacity) {
|
||||||
|
// Create new queue with updated capacity
|
||||||
|
BlockingQueue<QueuedJob> newQueue = new LinkedBlockingQueue<>(newCapacity);
|
||||||
|
|
||||||
|
// Transfer jobs from old queue to new queue
|
||||||
|
jobQueue.drainTo(newQueue);
|
||||||
|
jobQueue = newQueue;
|
||||||
|
|
||||||
|
currentQueueSize = jobQueue.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error updating queue capacity: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Processes jobs in the queue, executing them when resources permit. */
|
||||||
|
private void processQueue() {
|
||||||
|
// Jobs to execute after releasing the lock
|
||||||
|
java.util.List<QueuedJob> jobsToExecute = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
// First synchronized block: poll jobs from the queue and prepare them for execution
|
||||||
|
synchronized (queueLock) {
|
||||||
|
if (shuttingDown || jobQueue.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get current resource status
|
||||||
|
ResourceMonitor.ResourceStatus status = resourceMonitor.getCurrentStatus().get();
|
||||||
|
|
||||||
|
// Check if we should execute any jobs
|
||||||
|
boolean canExecuteJobs = (status != ResourceMonitor.ResourceStatus.CRITICAL);
|
||||||
|
|
||||||
|
if (!canExecuteJobs) {
|
||||||
|
// Under critical load, don't execute any jobs
|
||||||
|
log.debug("System under critical load, delaying job execution");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get jobs from the queue, up to a limit based on resource availability
|
||||||
|
int jobsToProcess =
|
||||||
|
Math.max(
|
||||||
|
1,
|
||||||
|
switch (status) {
|
||||||
|
case OK -> 3;
|
||||||
|
case WARNING -> 1;
|
||||||
|
case CRITICAL -> 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < jobsToProcess && !jobQueue.isEmpty(); i++) {
|
||||||
|
QueuedJob job = jobQueue.poll();
|
||||||
|
if (job == null) break;
|
||||||
|
|
||||||
|
// Check if it's been waiting too long
|
||||||
|
long waitTimeMs = Instant.now().toEpochMilli() - job.queuedAt.toEpochMilli();
|
||||||
|
if (waitTimeMs > maxWaitTimeMs) {
|
||||||
|
log.warn(
|
||||||
|
"Job {} exceeded maximum wait time ({} ms), executing anyway",
|
||||||
|
job.jobId,
|
||||||
|
waitTimeMs);
|
||||||
|
|
||||||
|
// Add a specific status to the job context that can be tracked
|
||||||
|
// This will be visible in the job status API
|
||||||
|
try {
|
||||||
|
TaskManager taskManager =
|
||||||
|
SpringContextHolder.getBean(TaskManager.class);
|
||||||
|
if (taskManager != null) {
|
||||||
|
taskManager.addNote(
|
||||||
|
job.jobId,
|
||||||
|
"QUEUED_TIMEOUT: Job waited in queue for "
|
||||||
|
+ (waitTimeMs / 1000)
|
||||||
|
+ " seconds, exceeding the maximum wait time of "
|
||||||
|
+ (maxWaitTimeMs / 1000)
|
||||||
|
+ " seconds.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(
|
||||||
|
"Failed to add timeout note to job {}: {}",
|
||||||
|
job.jobId,
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from our map
|
||||||
|
jobMap.remove(job.jobId);
|
||||||
|
currentQueueSize = jobQueue.size();
|
||||||
|
|
||||||
|
// Add to the list of jobs to execute outside the synchronized block
|
||||||
|
jobsToExecute.add(job);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error processing job queue: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now execute the jobs outside the synchronized block to avoid holding the lock
|
||||||
|
for (QueuedJob job : jobsToExecute) {
|
||||||
|
executeJob(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a job from the queue.
|
||||||
|
*
|
||||||
|
* @param job The job to execute
|
||||||
|
*/
|
||||||
|
private void executeJob(QueuedJob job) {
|
||||||
|
if (job.cancelled) {
|
||||||
|
log.debug("Job {} was cancelled, not executing", job.jobId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jobExecutor.execute(
|
||||||
|
() -> {
|
||||||
|
log.debug("Executing queued job {} (queued at {})", job.jobId, job.queuedAt);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Execute with timeout
|
||||||
|
Object result = executeWithTimeout(job.work, job.timeoutMs);
|
||||||
|
|
||||||
|
// Process the result
|
||||||
|
if (result instanceof ResponseEntity) {
|
||||||
|
job.future.complete((ResponseEntity<?>) result);
|
||||||
|
} else {
|
||||||
|
job.future.complete(ResponseEntity.ok(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(
|
||||||
|
"Error executing queued job {}: {}", job.jobId, e.getMessage(), e);
|
||||||
|
job.future.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a supplier with a timeout.
|
||||||
|
*
|
||||||
|
* @param supplier The supplier to execute
|
||||||
|
* @param timeoutMs The timeout in milliseconds
|
||||||
|
* @return The result from the supplier
|
||||||
|
* @throws Exception If there is an execution error
|
||||||
|
*/
|
||||||
|
private <T> T executeWithTimeout(Supplier<T> supplier, long timeoutMs) throws Exception {
|
||||||
|
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (timeoutMs <= 0) {
|
||||||
|
// No timeout
|
||||||
|
return future.join();
|
||||||
|
} else {
|
||||||
|
// With timeout
|
||||||
|
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
future.cancel(true);
|
||||||
|
throw new TimeoutException("Job timed out after " + timeoutMs + "ms");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw (Exception) e.getCause();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new InterruptedException("Job was interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a job is queued.
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return true if the job is queued
|
||||||
|
*/
|
||||||
|
public boolean isJobQueued(String jobId) {
|
||||||
|
return jobMap.containsKey(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current position of a job in the queue.
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return The position (0-based) or -1 if not found
|
||||||
|
*/
|
||||||
|
public int getJobPosition(String jobId) {
|
||||||
|
if (!jobMap.containsKey(jobId)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count positions
|
||||||
|
int position = 0;
|
||||||
|
for (QueuedJob job : jobQueue) {
|
||||||
|
if (job.jobId.equals(jobId)) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't find it in the queue but it's in the map,
|
||||||
|
// it might be executing already
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a queued job.
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return true if the job was cancelled, false if not found
|
||||||
|
*/
|
||||||
|
public boolean cancelJob(String jobId) {
|
||||||
|
QueuedJob job = jobMap.remove(jobId);
|
||||||
|
if (job != null) {
|
||||||
|
job.cancelled = true;
|
||||||
|
job.future.completeExceptionally(new RuntimeException("Job cancelled by user"));
|
||||||
|
|
||||||
|
// Try to remove from queue if it's still there
|
||||||
|
jobQueue.remove(job);
|
||||||
|
currentQueueSize = jobQueue.size();
|
||||||
|
|
||||||
|
log.debug("Job {} cancelled", jobId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get queue statistics.
|
||||||
|
*
|
||||||
|
* @return A map containing queue statistics
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getQueueStats() {
|
||||||
|
return Map.of(
|
||||||
|
"queuedJobs", jobQueue.size(),
|
||||||
|
"queueCapacity", getQueueCapacity(),
|
||||||
|
"totalQueuedJobs", totalQueuedJobs,
|
||||||
|
"rejectedJobs", rejectedJobs,
|
||||||
|
"resourceStatus", resourceMonitor.getCurrentStatus().get().name());
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.common.service;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
@ -7,9 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.common.model.PdfMetadata;
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PdfMetadataService {
|
public class PdfMetadataService {
|
@ -1,12 +1,21 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.common.service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.management.*;
|
import java.lang.management.GarbageCollectorMXBean;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.MemoryMXBean;
|
||||||
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
|
import java.lang.management.RuntimeMXBean;
|
||||||
|
import java.lang.management.ThreadMXBean;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -16,8 +25,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.posthog.java.PostHog;
|
import com.posthog.java.PostHog;
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PostHogService {
|
public class PostHogService {
|
||||||
@ -200,7 +208,7 @@ public class PostHogService {
|
|||||||
|
|
||||||
// New environment variables
|
// New environment variables
|
||||||
dockerMetrics.put("version_tag", System.getenv("VERSION_TAG"));
|
dockerMetrics.put("version_tag", System.getenv("VERSION_TAG"));
|
||||||
dockerMetrics.put("docker_enable_security", System.getenv("DOCKER_ENABLE_SECURITY"));
|
dockerMetrics.put("additional_features_off", System.getenv("ADDITIONAL_FEATURES_OFF"));
|
||||||
dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER"));
|
dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER"));
|
||||||
|
|
||||||
return dockerMetrics;
|
return dockerMetrics;
|
@ -0,0 +1,277 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.MemoryMXBean;
|
||||||
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors system resources (CPU, memory) to inform job scheduling decisions. Provides information
|
||||||
|
* about available resources to prevent overloading the system.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class ResourceMonitor {
|
||||||
|
|
||||||
|
@Value("${stirling.resource.memory.critical-threshold:0.9}")
|
||||||
|
private double memoryCriticalThreshold = 0.9; // 90% usage is critical
|
||||||
|
|
||||||
|
@Value("${stirling.resource.memory.high-threshold:0.75}")
|
||||||
|
private double memoryHighThreshold = 0.75; // 75% usage is high
|
||||||
|
|
||||||
|
@Value("${stirling.resource.cpu.critical-threshold:0.9}")
|
||||||
|
private double cpuCriticalThreshold = 0.9; // 90% usage is critical
|
||||||
|
|
||||||
|
@Value("${stirling.resource.cpu.high-threshold:0.75}")
|
||||||
|
private double cpuHighThreshold = 0.75; // 75% usage is high
|
||||||
|
|
||||||
|
@Value("${stirling.resource.monitor.interval-ms:60000}")
|
||||||
|
private long monitorIntervalMs = 60000; // 60 seconds
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||||
|
private final OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final AtomicReference<ResourceStatus> currentStatus =
|
||||||
|
new AtomicReference<>(ResourceStatus.OK);
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final AtomicReference<ResourceMetrics> latestMetrics =
|
||||||
|
new AtomicReference<>(new ResourceMetrics());
|
||||||
|
|
||||||
|
/** Represents the current status of system resources. */
|
||||||
|
public enum ResourceStatus {
|
||||||
|
/** Resources are available, normal operations can proceed */
|
||||||
|
OK,
|
||||||
|
|
||||||
|
/** Resources are under strain, consider queueing high-resource operations */
|
||||||
|
WARNING,
|
||||||
|
|
||||||
|
/** Resources are critically low, queue all operations */
|
||||||
|
CRITICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Detailed metrics about system resources. */
|
||||||
|
@Getter
|
||||||
|
public static class ResourceMetrics {
|
||||||
|
private final double cpuUsage;
|
||||||
|
private final double memoryUsage;
|
||||||
|
private final long freeMemoryBytes;
|
||||||
|
private final long totalMemoryBytes;
|
||||||
|
private final long maxMemoryBytes;
|
||||||
|
private final Instant timestamp;
|
||||||
|
|
||||||
|
public ResourceMetrics() {
|
||||||
|
this(0, 0, 0, 0, 0, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceMetrics(
|
||||||
|
double cpuUsage,
|
||||||
|
double memoryUsage,
|
||||||
|
long freeMemoryBytes,
|
||||||
|
long totalMemoryBytes,
|
||||||
|
long maxMemoryBytes,
|
||||||
|
Instant timestamp) {
|
||||||
|
this.cpuUsage = cpuUsage;
|
||||||
|
this.memoryUsage = memoryUsage;
|
||||||
|
this.freeMemoryBytes = freeMemoryBytes;
|
||||||
|
this.totalMemoryBytes = totalMemoryBytes;
|
||||||
|
this.maxMemoryBytes = maxMemoryBytes;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the age of these metrics.
|
||||||
|
*
|
||||||
|
* @return Duration since these metrics were collected
|
||||||
|
*/
|
||||||
|
public Duration getAge() {
|
||||||
|
return Duration.between(timestamp, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if these metrics are stale (older than threshold).
|
||||||
|
*
|
||||||
|
* @param thresholdMs Staleness threshold in milliseconds
|
||||||
|
* @return true if metrics are stale
|
||||||
|
*/
|
||||||
|
public boolean isStale(long thresholdMs) {
|
||||||
|
return getAge().toMillis() > thresholdMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initialize() {
|
||||||
|
log.debug("Starting resource monitoring with interval of {}ms", monitorIntervalMs);
|
||||||
|
scheduler.scheduleAtFixedRate(
|
||||||
|
this::updateResourceMetrics, 0, monitorIntervalMs, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdown() {
|
||||||
|
log.info("Shutting down resource monitoring");
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the resource metrics by sampling current system state. */
|
||||||
|
private void updateResourceMetrics() {
|
||||||
|
try {
|
||||||
|
// Get CPU usage
|
||||||
|
double cpuUsage = osMXBean.getSystemLoadAverage() / osMXBean.getAvailableProcessors();
|
||||||
|
if (cpuUsage < 0) cpuUsage = getAlternativeCpuLoad(); // Fallback if not available
|
||||||
|
|
||||||
|
// Get memory usage
|
||||||
|
long heapUsed = memoryMXBean.getHeapMemoryUsage().getUsed();
|
||||||
|
long nonHeapUsed = memoryMXBean.getNonHeapMemoryUsage().getUsed();
|
||||||
|
long totalUsed = heapUsed + nonHeapUsed;
|
||||||
|
|
||||||
|
long maxMemory = Runtime.getRuntime().maxMemory();
|
||||||
|
long totalMemory = Runtime.getRuntime().totalMemory();
|
||||||
|
long freeMemory = Runtime.getRuntime().freeMemory();
|
||||||
|
|
||||||
|
double memoryUsage = (double) totalUsed / maxMemory;
|
||||||
|
|
||||||
|
// Create new metrics
|
||||||
|
ResourceMetrics metrics =
|
||||||
|
new ResourceMetrics(
|
||||||
|
cpuUsage,
|
||||||
|
memoryUsage,
|
||||||
|
freeMemory,
|
||||||
|
totalMemory,
|
||||||
|
maxMemory,
|
||||||
|
Instant.now());
|
||||||
|
latestMetrics.set(metrics);
|
||||||
|
|
||||||
|
// Determine system status
|
||||||
|
ResourceStatus newStatus;
|
||||||
|
if (cpuUsage > cpuCriticalThreshold || memoryUsage > memoryCriticalThreshold) {
|
||||||
|
newStatus = ResourceStatus.CRITICAL;
|
||||||
|
} else if (cpuUsage > cpuHighThreshold || memoryUsage > memoryHighThreshold) {
|
||||||
|
newStatus = ResourceStatus.WARNING;
|
||||||
|
} else {
|
||||||
|
newStatus = ResourceStatus.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status if it changed
|
||||||
|
ResourceStatus oldStatus = currentStatus.getAndSet(newStatus);
|
||||||
|
if (oldStatus != newStatus) {
|
||||||
|
log.info("System resource status changed from {} to {}", oldStatus, newStatus);
|
||||||
|
log.info(
|
||||||
|
"Current metrics - CPU: {}%, Memory: {}%, Free Memory: {} MB",
|
||||||
|
String.format("%.1f", cpuUsage * 100), String.format("%.1f", memoryUsage * 100), freeMemory / (1024 * 1024));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error updating resource metrics: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative method to estimate CPU load if getSystemLoadAverage() is not available. This is a
|
||||||
|
* fallback and less accurate than the official JMX method.
|
||||||
|
*
|
||||||
|
* @return Estimated CPU load as a value between 0.0 and 1.0
|
||||||
|
*/
|
||||||
|
private double getAlternativeCpuLoad() {
|
||||||
|
try {
|
||||||
|
// Try to get CPU time if available through reflection
|
||||||
|
// This is a fallback since we can't directly cast to platform-specific classes
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method m =
|
||||||
|
osMXBean.getClass().getDeclaredMethod("getProcessCpuLoad");
|
||||||
|
m.setAccessible(true);
|
||||||
|
return (double) m.invoke(osMXBean);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Try the older method
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method m =
|
||||||
|
osMXBean.getClass().getDeclaredMethod("getSystemCpuLoad");
|
||||||
|
m.setAccessible(true);
|
||||||
|
return (double) m.invoke(osMXBean);
|
||||||
|
} catch (Exception e2) {
|
||||||
|
log.trace(
|
||||||
|
"Could not get CPU load through reflection, assuming moderate load (0.5)");
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.trace("Could not get CPU load, assuming moderate load (0.5)");
|
||||||
|
return 0.5; // Default to moderate load
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the dynamic job queue capacity based on current resource usage.
|
||||||
|
*
|
||||||
|
* @param baseCapacity The base capacity when system is under minimal load
|
||||||
|
* @param minCapacity The minimum capacity to maintain even under high load
|
||||||
|
* @return The calculated job queue capacity
|
||||||
|
*/
|
||||||
|
public int calculateDynamicQueueCapacity(int baseCapacity, int minCapacity) {
|
||||||
|
ResourceMetrics metrics = latestMetrics.get();
|
||||||
|
ResourceStatus status = currentStatus.get();
|
||||||
|
|
||||||
|
// Simple linear reduction based on memory and CPU load
|
||||||
|
double capacityFactor =
|
||||||
|
switch (status) {
|
||||||
|
case OK -> 1.0;
|
||||||
|
case WARNING -> 0.6;
|
||||||
|
case CRITICAL -> 0.3;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply additional reduction based on specific memory pressure
|
||||||
|
if (metrics.memoryUsage > 0.8) {
|
||||||
|
capacityFactor *= 0.5; // Further reduce capacity under memory pressure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate capacity with minimum safeguard
|
||||||
|
int capacity = (int) Math.max(minCapacity, Math.ceil(baseCapacity * capacityFactor));
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Dynamic queue capacity: {} (base: {}, factor: {:.2f}, status: {})",
|
||||||
|
capacity,
|
||||||
|
baseCapacity,
|
||||||
|
capacityFactor,
|
||||||
|
status);
|
||||||
|
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a job with the given weight can be executed immediately or should be queued based
|
||||||
|
* on current resource availability.
|
||||||
|
*
|
||||||
|
* @param resourceWeight The resource weight of the job (1-100)
|
||||||
|
* @return true if the job should be queued, false if it can run immediately
|
||||||
|
*/
|
||||||
|
public boolean shouldQueueJob(int resourceWeight) {
|
||||||
|
ResourceStatus status = currentStatus.get();
|
||||||
|
|
||||||
|
// Always run lightweight jobs (weight < 20) unless critical
|
||||||
|
if (resourceWeight < 20 && status != ResourceStatus.CRITICAL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Medium weight jobs run immediately if resources are OK
|
||||||
|
if (resourceWeight < 60 && status == ResourceStatus.OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heavy jobs (weight >= 60) and any job during WARNING/CRITICAL should be queued
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,293 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.model.job.JobResult;
|
||||||
|
import stirling.software.common.model.job.JobStats;
|
||||||
|
|
||||||
|
/** Manages async tasks and their results */
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class TaskManager {
|
||||||
|
private final Map<String, JobResult> jobResults = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Value("${stirling.jobResultExpiryMinutes:30}")
|
||||||
|
private int jobResultExpiryMinutes = 30;
|
||||||
|
|
||||||
|
private final FileStorage fileStorage;
|
||||||
|
private final ScheduledExecutorService cleanupExecutor =
|
||||||
|
Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
/** Initialize the task manager and start the cleanup scheduler */
|
||||||
|
public TaskManager(FileStorage fileStorage) {
|
||||||
|
this.fileStorage = fileStorage;
|
||||||
|
|
||||||
|
// Schedule periodic cleanup of old job results
|
||||||
|
cleanupExecutor.scheduleAtFixedRate(
|
||||||
|
this::cleanupOldJobs,
|
||||||
|
10, // Initial delay
|
||||||
|
10, // Interval
|
||||||
|
TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Task manager initialized with job result expiry of {} minutes",
|
||||||
|
jobResultExpiryMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new task with the given job ID
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
*/
|
||||||
|
public void createTask(String jobId) {
|
||||||
|
jobResults.put(jobId, JobResult.createNew(jobId));
|
||||||
|
log.debug("Created task with job ID: {}", jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the result of a task as a general object
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param result The result object
|
||||||
|
*/
|
||||||
|
public void setResult(String jobId, Object result) {
|
||||||
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
|
jobResult.completeWithResult(result);
|
||||||
|
log.debug("Set result for job ID: {}", jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the result of a task as a file
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param fileId The file ID
|
||||||
|
* @param originalFileName The original file name
|
||||||
|
* @param contentType The content type of the file
|
||||||
|
*/
|
||||||
|
public void setFileResult(
|
||||||
|
String jobId, String fileId, String originalFileName, String contentType) {
|
||||||
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
|
jobResult.completeWithFile(fileId, originalFileName, contentType);
|
||||||
|
log.debug("Set file result for job ID: {} with file ID: {}", jobId, fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an error for a task
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param error The error message
|
||||||
|
*/
|
||||||
|
public void setError(String jobId, String error) {
|
||||||
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
|
jobResult.failWithError(error);
|
||||||
|
log.debug("Set error for job ID: {}: {}", jobId, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a task as complete
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
*/
|
||||||
|
public void setComplete(String jobId) {
|
||||||
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
|
if (jobResult.getResult() == null
|
||||||
|
&& jobResult.getFileId() == null
|
||||||
|
&& jobResult.getError() == null) {
|
||||||
|
// If no result or error has been set, mark it as complete with an empty result
|
||||||
|
jobResult.completeWithResult("Task completed successfully");
|
||||||
|
}
|
||||||
|
log.debug("Marked job ID: {} as complete", jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a task is complete
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return true if the task is complete, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isComplete(String jobId) {
|
||||||
|
JobResult result = jobResults.get(jobId);
|
||||||
|
return result != null && result.isComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the result of a task
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return The result object, or null if the task doesn't exist or is not complete
|
||||||
|
*/
|
||||||
|
public JobResult getJobResult(String jobId) {
|
||||||
|
return jobResults.get(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a note to a task. Notes are informational messages that can be attached to a job for
|
||||||
|
* tracking purposes.
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param note The note to add
|
||||||
|
* @return true if the note was added successfully, false if the job doesn't exist
|
||||||
|
*/
|
||||||
|
public boolean addNote(String jobId, String note) {
|
||||||
|
JobResult jobResult = jobResults.get(jobId);
|
||||||
|
if (jobResult != null) {
|
||||||
|
jobResult.addNote(note);
|
||||||
|
log.debug("Added note to job ID: {}: {}", jobId, note);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log.warn("Attempted to add note to non-existent job ID: {}", jobId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get statistics about all jobs in the system
|
||||||
|
*
|
||||||
|
* @return Job statistics
|
||||||
|
*/
|
||||||
|
public JobStats getJobStats() {
|
||||||
|
int totalJobs = jobResults.size();
|
||||||
|
int activeJobs = 0;
|
||||||
|
int completedJobs = 0;
|
||||||
|
int failedJobs = 0;
|
||||||
|
int successfulJobs = 0;
|
||||||
|
int fileResultJobs = 0;
|
||||||
|
|
||||||
|
LocalDateTime oldestActiveJobTime = null;
|
||||||
|
LocalDateTime newestActiveJobTime = null;
|
||||||
|
long totalProcessingTimeMs = 0;
|
||||||
|
|
||||||
|
for (JobResult result : jobResults.values()) {
|
||||||
|
if (result.isComplete()) {
|
||||||
|
completedJobs++;
|
||||||
|
|
||||||
|
// Calculate processing time for completed jobs
|
||||||
|
if (result.getCreatedAt() != null && result.getCompletedAt() != null) {
|
||||||
|
long processingTimeMs =
|
||||||
|
java.time.Duration.between(
|
||||||
|
result.getCreatedAt(), result.getCompletedAt())
|
||||||
|
.toMillis();
|
||||||
|
totalProcessingTimeMs += processingTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.getError() != null) {
|
||||||
|
failedJobs++;
|
||||||
|
} else {
|
||||||
|
successfulJobs++;
|
||||||
|
if (result.getFileId() != null) {
|
||||||
|
fileResultJobs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeJobs++;
|
||||||
|
|
||||||
|
// Track oldest and newest active jobs
|
||||||
|
if (result.getCreatedAt() != null) {
|
||||||
|
if (oldestActiveJobTime == null
|
||||||
|
|| result.getCreatedAt().isBefore(oldestActiveJobTime)) {
|
||||||
|
oldestActiveJobTime = result.getCreatedAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newestActiveJobTime == null
|
||||||
|
|| result.getCreatedAt().isAfter(newestActiveJobTime)) {
|
||||||
|
newestActiveJobTime = result.getCreatedAt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate average processing time
|
||||||
|
long averageProcessingTimeMs =
|
||||||
|
completedJobs > 0 ? totalProcessingTimeMs / completedJobs : 0;
|
||||||
|
|
||||||
|
return JobStats.builder()
|
||||||
|
.totalJobs(totalJobs)
|
||||||
|
.activeJobs(activeJobs)
|
||||||
|
.completedJobs(completedJobs)
|
||||||
|
.failedJobs(failedJobs)
|
||||||
|
.successfulJobs(successfulJobs)
|
||||||
|
.fileResultJobs(fileResultJobs)
|
||||||
|
.oldestActiveJobTime(oldestActiveJobTime)
|
||||||
|
.newestActiveJobTime(newestActiveJobTime)
|
||||||
|
.averageProcessingTimeMs(averageProcessingTimeMs)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create a job result
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return The job result
|
||||||
|
*/
|
||||||
|
private JobResult getOrCreateJobResult(String jobId) {
|
||||||
|
return jobResults.computeIfAbsent(jobId, JobResult::createNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clean up old completed job results */
|
||||||
|
public void cleanupOldJobs() {
|
||||||
|
LocalDateTime expiryThreshold =
|
||||||
|
LocalDateTime.now().minus(jobResultExpiryMinutes, ChronoUnit.MINUTES);
|
||||||
|
int removedCount = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Map.Entry<String, JobResult> entry : jobResults.entrySet()) {
|
||||||
|
JobResult result = entry.getValue();
|
||||||
|
|
||||||
|
// Remove completed jobs that are older than the expiry threshold
|
||||||
|
if (result.isComplete()
|
||||||
|
&& result.getCompletedAt() != null
|
||||||
|
&& result.getCompletedAt().isBefore(expiryThreshold)) {
|
||||||
|
|
||||||
|
// If the job has a file result, delete the file
|
||||||
|
if (result.getFileId() != null) {
|
||||||
|
try {
|
||||||
|
fileStorage.deleteFile(result.getFileId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to delete file for job {}: {}",
|
||||||
|
entry.getKey(),
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the job result
|
||||||
|
jobResults.remove(entry.getKey());
|
||||||
|
removedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedCount > 0) {
|
||||||
|
log.info("Cleaned up {} expired job results", removedCount);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error during job cleanup: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shutdown the cleanup executor */
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdown() {
|
||||||
|
try {
|
||||||
|
log.info("Shutting down job result cleanup executor");
|
||||||
|
cleanupExecutor.shutdown();
|
||||||
|
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
cleanupExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
cleanupExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.common.service;
|
||||||
|
|
||||||
public interface UserServiceInterface {
|
public interface UserServiceInterface {
|
||||||
String getApiKeyForUser(String username);
|
String getApiKeyForUser(String username);
|
@ -1,10 +1,10 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
public class CheckProgramInstall {
|
public class CheckProgramInstall {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import org.owasp.html.HtmlPolicyBuilder;
|
import org.owasp.html.HtmlPolicyBuilder;
|
||||||
import org.owasp.html.PolicyFactory;
|
import org.owasp.html.PolicyFactory;
|
1750
common/src/main/java/stirling/software/common/util/EmlToPdf.java
Normal file
1750
common/src/main/java/stirling/software/common/util/EmlToPdf.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
@ -0,0 +1,31 @@
|
|||||||
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ExecutorFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ExecutorService using virtual threads if available (Java 21+), or falls back to a
|
||||||
|
* cached thread pool on older Java versions.
|
||||||
|
*/
|
||||||
|
public static ExecutorService newVirtualOrCachedThreadExecutor() {
|
||||||
|
try {
|
||||||
|
ExecutorService executor =
|
||||||
|
(ExecutorService)
|
||||||
|
Executors.class
|
||||||
|
.getMethod("newVirtualThreadPerTaskExecutor")
|
||||||
|
.invoke(null);
|
||||||
|
return executor;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
log.debug("Virtual threads not available; falling back to cached thread pool.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Error initializing virtual thread executor: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Executors.newCachedThreadPool();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import static java.nio.file.StandardWatchEventKinds.*;
|
import static java.nio.file.StandardWatchEventKinds.*;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.RuntimePathConfig;
|
import stirling.software.common.configuration.RuntimePathConfig;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -16,8 +16,8 @@ import java.util.zip.ZipOutputStream;
|
|||||||
|
|
||||||
import io.github.pixee.security.ZipSecurity;
|
import io.github.pixee.security.ZipSecurity;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
public class FileToPdf {
|
public class FileToPdf {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@ -13,6 +13,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@ -27,8 +28,7 @@ import io.github.pixee.security.Urls;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.common.configuration.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.config.YamlHelper;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GeneralUtils {
|
public class GeneralUtils {
|
||||||
@ -200,11 +200,11 @@ public class GeneralUtils {
|
|||||||
if (bytes < 1024) {
|
if (bytes < 1024) {
|
||||||
return bytes + " B";
|
return bytes + " B";
|
||||||
} else if (bytes < 1024 * 1024) {
|
} else if (bytes < 1024 * 1024) {
|
||||||
return String.format("%.2f KB", bytes / 1024.0);
|
return String.format(Locale.US, "%.2f KB", bytes / 1024.0);
|
||||||
} else if (bytes < 1024 * 1024 * 1024) {
|
} else if (bytes < 1024 * 1024 * 1024) {
|
||||||
return String.format("%.2f MB", bytes / (1024.0 * 1024.0));
|
return String.format(Locale.US, "%.2f MB", bytes / (1024.0 * 1024.0));
|
||||||
} else {
|
} else {
|
||||||
return String.format("%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
return String.format(Locale.US, "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -28,7 +28,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
@ -35,7 +35,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PdfUtils {
|
public class PdfUtils {
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -17,7 +17,7 @@ import io.github.pixee.security.BoundedLineReader;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ProcessExecutor {
|
public class ProcessExecutor {
|
@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -1,10 +1,11 @@
|
|||||||
package stirling.software.SPDF.utils.validation;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.util.Collection;
|
import static stirling.software.common.util.ValidationUtils.isCollectionEmpty;
|
||||||
|
import static stirling.software.common.util.ValidationUtils.isStringEmpty;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.provider.Provider;
|
import stirling.software.common.model.oauth2.Provider;
|
||||||
|
|
||||||
public class Validator {
|
public class ProviderUtils {
|
||||||
|
|
||||||
public static boolean validateProvider(Provider provider) {
|
public static boolean validateProvider(Provider provider) {
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
@ -25,12 +26,4 @@ public class Validator {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStringEmpty(String input) {
|
|
||||||
return input == null || input.isBlank();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCollectionEmpty(Collection<String> input) {
|
|
||||||
return input == null || input.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
public class RequestUriUtils {
|
public class RequestUriUtils {
|
||||||
|
|
||||||
public static boolean isStaticResource(String requestURI) {
|
public static boolean isStaticResource(String requestURI) {
|
||||||
|
|
||||||
return isStaticResource("", requestURI);
|
return isStaticResource("", requestURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStaticResource(String contextPath, String requestURI) {
|
public static boolean isStaticResource(String contextPath, String requestURI) {
|
||||||
|
|
||||||
return requestURI.startsWith(contextPath + "/css/")
|
return requestURI.startsWith(contextPath + "/css/")
|
||||||
|| requestURI.startsWith(contextPath + "/fonts/")
|
|| requestURI.startsWith(contextPath + "/fonts/")
|
||||||
|| requestURI.startsWith(contextPath + "/js/")
|
|| requestURI.startsWith(contextPath + "/js/")
|
||||||
@ -21,6 +19,7 @@ public class RequestUriUtils {
|
|||||||
|| requestURI.endsWith(".svg")
|
|| requestURI.endsWith(".svg")
|
||||||
|| requestURI.endsWith(".png")
|
|| requestURI.endsWith(".png")
|
||||||
|| requestURI.endsWith(".ico")
|
|| requestURI.endsWith(".ico")
|
||||||
|
|| requestURI.endsWith(".txt")
|
||||||
|| requestURI.endsWith(".webmanifest")
|
|| requestURI.endsWith(".webmanifest")
|
||||||
|| requestURI.startsWith(contextPath + "/api/v1/info/status");
|
|| requestURI.startsWith(contextPath + "/api/v1/info/status");
|
||||||
}
|
}
|
||||||
@ -37,6 +36,7 @@ public class RequestUriUtils {
|
|||||||
|| requestURI.endsWith(".png")
|
|| requestURI.endsWith(".png")
|
||||||
|| requestURI.endsWith(".ico")
|
|| requestURI.endsWith(".ico")
|
||||||
|| requestURI.endsWith(".css")
|
|| requestURI.endsWith(".css")
|
||||||
|
|| requestURI.endsWith(".txt")
|
||||||
|| requestURI.endsWith(".map")
|
|| requestURI.endsWith(".map")
|
||||||
|| requestURI.endsWith(".svg")
|
|| requestURI.endsWith(".svg")
|
||||||
|| requestURI.endsWith("popularity.txt")
|
|| requestURI.endsWith("popularity.txt")
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user