Skip to content

Commit

Permalink
#2270: External DB Support (#2457)
Browse files Browse the repository at this point in the history
# Description

External DB support for Stirling PDF. You can now choose between the
default H2 or PostgreSQL by setting the new `enableCustomDatabase`
property to `true` or `false`.

To enable your own custom (PostgreSQL) database:
- Set `enableCustomDatabase` to `true`
- Add your database url to `customDatabaseUrl`
- Set your `username` and `password`

Closes #2270 

## Checklist

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have performed a self-review of my own code
- [x] I have attached images of the change if it is UI based
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [x] My changes generate no new warnings
- [x] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
  • Loading branch information
DarioGii authored Jan 6, 2025
1 parent 7382efd commit 41dce06
Show file tree
Hide file tree
Showing 32 changed files with 988 additions and 531 deletions.
25 changes: 25 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ sourceSets {

}
}

test {
java {
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
exclude "stirling/software/SPDF/config/security/**"
exclude "stirling/software/SPDF/controller/api/UserControllerTest.java"
exclude "stirling/software/SPDF/controller/api/DatabaseControllerTest.java"
exclude "stirling/software/SPDF/controller/web/AccountWebControllerTest.java"
exclude "stirling/software/SPDF/controller/web/DatabaseWebControllerTest.java"
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationTokenTest.java"
exclude "stirling/software/SPDF/model/AttemptCounterTest.java"
exclude "stirling/software/SPDF/model/AuthorityTest.java"
exclude "stirling/software/SPDF/model/PersistentLoginTest.java"
exclude "stirling/software/SPDF/model/SessionEntityTest.java"
exclude "stirling/software/SPDF/model/UserTest.java"
exclude "stirling/software/SPDF/repository/**"
}

if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
}
}
}

openApi {
Expand Down Expand Up @@ -299,10 +322,12 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"

implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation "org.springframework:spring-jdbc:6.2.1"

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.4"
constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
Expand Down
63 changes: 63 additions & 0 deletions exampleYmlFiles/docker-compose-latest-fat-security-postgres.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat-Postgres
image: stirlingtools/stirling-pdf:latest-fat-postgres
deploy:
resources:
limits:
memory: 4G
depends_on:
- db
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'" ]
interval: 5s
timeout: 10s
retries: 16
ports:
- 8080:8080
volumes:
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "false"
PUID: 1002
PGID: 1002
UMASK: "022"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security and PostgreSQL
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat-PostgreSQL
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "true"
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "true"
SYSTEM_DATASOURCE_CUSTOMDATABASEURL: "jdbc:postgresql://db:5432/stirling_pdf"
SYSTEM_DATASOURCE_USERNAME: "admin"
SYSTEM_DATASOURCE_PASSWORD: "stirling"
restart: on-failure:5

db:
image: 'postgres:17.2-alpine'
restart: on-failure:5
container_name: db
ports:
- "5432:5432"
environment:
POSTGRES_DB: "stirling_pdf"
POSTGRES_USER: "admin"
POSTGRES_PASSWORD: "stirling"
shm_size: "512mb"
deploy:
resources:
limits:
memory: 512m
cpus: "0.5"
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U admin stirling_pdf" ]
interval: 1s
timeout: 5s
retries: 10
volumes:
- ./stirling/latest/data:/pgdata
6 changes: 3 additions & 3 deletions exampleYmlFiles/docker-compose-latest-fat-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ services:
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "false"
Expand Down
6 changes: 3 additions & 3 deletions exampleYmlFiles/docker-compose-latest-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ services:
ports:
- "8080:8080"
volumes:
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
- ./stirling/latest/data:/usr/share/tessdata:rw
- ./stirling/latest/config:/configs:rw
- ./stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class LicenseKeyChecker {

private final ApplicationProperties applicationProperties;

private boolean enterpriseEnbaledResult = false;
private boolean enterpriseEnabledResult = false;

@Autowired
public LicenseKeyChecker(
Expand All @@ -35,12 +35,12 @@ public void checkLicensePeriodically() {

private void checkLicense() {
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
enterpriseEnbaledResult = false;
enterpriseEnabledResult = false;
} else {
enterpriseEnbaledResult =
enterpriseEnabledResult =
licenseService.verifyLicense(
applicationProperties.getEnterpriseEdition().getKey());
if (enterpriseEnbaledResult) {
if (enterpriseEnabledResult) {
log.info("License key is valid.");
} else {
log.info("License key is invalid.");
Expand All @@ -55,6 +55,6 @@ public void updateLicenseKey(String newKey) throws IOException {
}

public boolean getEnterpriseEnabledResult() {
return enterpriseEnbaledResult;
return enterpriseEnabledResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,22 @@
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties;

@SpringBootApplication
@EnableScheduling
@Slf4j
public class SPdfApplication {
@EnableScheduling
@SpringBootApplication
public class SPDFApplication {

private static String baseUrlStatic;
private static String serverPortStatic;
private static String baseUrlStatic;

private final Environment env;
private final ApplicationProperties applicationProperties;
private final WebBrowser webBrowser;

@Value("${baseUrl:http://localhost}")
private String baseUrl;

public SPdfApplication(
public SPDFApplication(
Environment env,
ApplicationProperties applicationProperties,
@Autowired(required = false) WebBrowser webBrowser) {
Expand All @@ -51,42 +52,28 @@ public SPdfApplication(
this.webBrowser = webBrowser;
}

// Optionally keep this method if you want to provide a manual port-incrementation fallback.
private static String findAvailablePort(int startPort) {
int port = startPort;
while (!isPortAvailable(port)) {
port++;
}
return String.valueOf(port);
}

private static boolean isPortAvailable(int port) {
try (ServerSocket socket = new ServerSocket(port)) {
return true;
} catch (IOException e) {
return false;
}
}

public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class);
SpringApplication app = new SpringApplication(SPDFApplication.class);

Properties props = new Properties();

if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
System.setProperty("java.awt.headless", "false");
app.setHeadless(false);
props.put("java.awt.headless", "false");
props.put("spring.main.web-application-type", "servlet");
}
app.setAdditionalProfiles("default");

app.setAdditionalProfiles(getActiveProfile(args));

ConfigInitializer initializer = new ConfigInitializer();
try {
initializer.ensureConfigExists();
} catch (IOException | URISyntaxException e) {
log.error("Error initialising configuration", e);
}

Map<String, String> propertyFiles = new HashMap<>();

// External config files
log.info("Settings file: {}", InstallationPathConfig.getSettingsPath());
if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) {
Expand All @@ -98,6 +85,7 @@ public static void main(String[] args) throws IOException, InterruptedException
"External configuration file '{}' does not exist.",
InstallationPathConfig.getSettingsPath());
}

if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) {
String existingLocation =
propertyFiles.getOrDefault("spring.config.additional-location", "");
Expand All @@ -113,50 +101,30 @@ public static void main(String[] args) throws IOException, InterruptedException
InstallationPathConfig.getCustomSettingsPath());
}
Properties finalProps = new Properties();

if (!propertyFiles.isEmpty()) {
finalProps.putAll(
Collections.singletonMap(
"spring.config.additional-location",
propertyFiles.get("spring.config.additional-location")));
}

if (!props.isEmpty()) {
finalProps.putAll(props);
}
app.setDefaultProperties(finalProps);

app.run(args);

// Ensure directories are created
try {
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));
} catch (Exception e) {
log.error("Error creating directories: {}", e.getMessage());
}
printStartupLogs();
}

private static void printStartupLogs() {
log.info("Stirling-PDF Started.");
String url = baseUrlStatic + ":" + getStaticPort();
log.info("Navigate to {}", url);
}

public static String getStaticBaseUrl() {
return baseUrlStatic;
}

public static String getStaticPort() {
return serverPortStatic;
}

@Value("${server.port:8080}")
public void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
SPdfApplication.serverPortStatic = // This will let Spring Boot assign an available port
"0";
} else {
SPdfApplication.serverPortStatic = port;
}
printStartupLogs();
}

@PostConstruct
Expand Down Expand Up @@ -189,17 +157,73 @@ public void init() {
log.info("Running configs {}", applicationProperties.toString());
}

@Value("${server.port:8080}")
public void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
SPDFApplication.serverPortStatic =
"0"; // This will let Spring Boot assign an available port
} else {
SPDFApplication.serverPortStatic = port;
}
}

@PreDestroy
public void cleanup() {
if (webBrowser != null) {
webBrowser.cleanup();
}
}

private static void printStartupLogs() {
log.info("Stirling-PDF Started.");
String url = baseUrlStatic + ":" + getStaticPort();
log.info("Navigate to {}", url);
}

private static String[] getActiveProfile(String[] args) {
if (args == null) {
return new String[] {"default"};
}

for (String arg : args) {
if (arg.contains("spring.profiles.active")) {
return arg.substring(args[0].indexOf('=') + 1).split(", ");
}
}

return new String[] {"default"};
}

private static boolean isPortAvailable(int port) {
try (ServerSocket socket = new ServerSocket(port)) {
return true;
} catch (IOException e) {
return false;
}
}

// Optionally keep this method if you want to provide a manual port-incrementation fallback.
private static String findAvailablePort(int startPort) {
int port = startPort;
while (!isPortAvailable(port)) {
port++;
}
return String.valueOf(port);
}

public static String getStaticBaseUrl() {
return baseUrlStatic;
}

public String getNonStaticBaseUrl() {
return baseUrlStatic;
}

public static String getStaticPort() {
return serverPortStatic;
}

public String getNonStaticPort() {
return serverPortStatic;
}
Expand Down

This file was deleted.

Loading

0 comments on commit 41dce06

Please sign in to comment.