diff --git a/README.md b/README.md
index 6240f01..863a3f6 100644
--- a/README.md
+++ b/README.md
@@ -298,7 +298,10 @@ Date: Fri, 25 Nov 2022 06:35:06 GMT
The `audit-base` project provides auditing functionality for easier investigation of issues. Audit records are stored in a database and can be easily queried. The auditing library also handles removal of old audit records.
-The audit library requires one database table `audit_log` and optionally the second table `audit_params` for logging detail parameters. The DDL is available for the following databases:
+The audit library requires one database table `audit_log` and optionally the second table `audit_params` for logging detail parameters.
+Also the `shedlock` table is required for locking scheduled tasks.
+
+The DDL is available for the following databases:
- [DDL for MySQL](./docs/sql/mysql/create_schema.sql)
- [DDL for Oracle](./docs/sql/oracle/create_schema.sql)
- [DDL for PostgreSQL](./docs/sql/postgresql/create_schema.sql)
@@ -306,7 +309,7 @@ The audit library requires one database table `audit_log` and optionally the sec
### Configuration
The following configuration is required for integration of the auditing library:
-- Enable scheduling on the application using `@EnableScheduling` annotation on class annotated with `@SpringBootApplication` so that the `flush` and `cleanup` functionality can be scheduled.
+- Enable scheduling on the application using `@EnableScheduling` annotation on class annotated with `@SpringBootApplication` so that the `flush` and `cleanup` functionality can be scheduled. In order to enable schedule locking use `@EnableSchedulerLock` annotation and configure the `LockProvider` bean, see [ShedLock documentation](https://github.com/lukas-krecan/ShedLock) for details.
- Add the `com.wultra.core.audit.base` package to the `@ComponentScan`, e.g. `@ComponentScan(basePackages = {"...", "com.wultra.core.audit.base"})`, so that the annotations used in auditing library can be discovered.
- Configure the `spring.application.name` property to enable storing application name with audit records.
@@ -318,7 +321,10 @@ The following properties can be configured in case the default configuration nee
- `audit.db.table.log.name` - name of audit log database table (default: `audit_log`)
- `audit.db.table.param.name` - name of audit parameters database table (default: `audit_param`)
- `audit.db.table.param.enabled` - flag if logging params to parameters database is enabled (default: `false`)
-- `audit.db.batch.size` - database batch size (default: `1000`)
+- `audit.db.batch.size` - database batch size (default: `1000`)
+- `audit.cleanup.cron` - A cron expression for the cleanup job. (default: `0 0 * * * *`, use `-` to turn it off completely)
+- `audit.cleanup.lockAtLeastFor` - The lock will be held at least for given duration. (default: `5s`)
+- `audit.cleanup.lockAtMostFor` - The lock will be held at most for given duration. (default: `30m`)
You can configure database schema used by the auditing library using regular Spring JPA/Hibernate property in your application:
- `spring.jpa.properties.hibernate.default_schema` - database database schema (default: none)
diff --git a/annotations/pom.xml b/annotations/pom.xml
index fb7fe9c..2edb9ec 100644
--- a/annotations/pom.xml
+++ b/annotations/pom.xml
@@ -7,7 +7,7 @@
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
annotations
diff --git a/audit-base/pom.xml b/audit-base/pom.xml
index 4cd56a8..bf885ba 100644
--- a/audit-base/pom.xml
+++ b/audit-base/pom.xml
@@ -6,7 +6,7 @@
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
audit-base
@@ -31,6 +31,21 @@
jakarta.annotation
jakarta.annotation-api
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-provider-jdbc-template
+ ${shedlock.version}
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-spring
+ ${shedlock.version}
+
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/audit-base/src/main/java/com/wultra/core/audit/base/database/DatabaseAuditWriter.java b/audit-base/src/main/java/com/wultra/core/audit/base/database/DatabaseAuditWriter.java
index a4d75ea..e4f64c1 100644
--- a/audit-base/src/main/java/com/wultra/core/audit/base/database/DatabaseAuditWriter.java
+++ b/audit-base/src/main/java/com/wultra/core/audit/base/database/DatabaseAuditWriter.java
@@ -23,6 +23,8 @@
import com.wultra.core.audit.base.util.JsonUtil;
import com.wultra.core.audit.base.util.StringUtil;
import jakarta.annotation.PreDestroy;
+import net.javacrumbs.shedlock.core.LockAssert;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -60,6 +62,8 @@ public class DatabaseAuditWriter implements AuditWriter {
private static final Logger logger = LoggerFactory.getLogger(DatabaseAuditWriter.class);
private static final String SPRING_FRAMEWORK_PACKAGE_PREFIX = "org.springframework";
+ private static final String JDK_INTERNAL_REFLECT_PACKAGE_PREFIX = "jdk.internal.reflect";
+ private static final String JAVA_LANG_REFLECT_PACKAGE_PREFIX = "java.lang.reflect";
private final BlockingQueue queue;
private final JdbcTemplate jdbcTemplate;
@@ -133,9 +137,11 @@ private void prepareSqlInsertQueries() {
@Override
public void write(AuditRecord auditRecord) {
- List packageFilter = new ArrayList<>();
- packageFilter.add(this.getClass().getPackage().getName());
- packageFilter.add(SPRING_FRAMEWORK_PACKAGE_PREFIX);
+ final List packageFilter = List.of(
+ this.getClass().getPackage().getName(),
+ SPRING_FRAMEWORK_PACKAGE_PREFIX,
+ JDK_INTERNAL_REFLECT_PACKAGE_PREFIX,
+ JAVA_LANG_REFLECT_PACKAGE_PREFIX);
auditRecord.setCallingClass(ClassUtil.getCallingClass(packageFilter));
auditRecord.setThreadName(Thread.currentThread().getName());
try {
@@ -283,12 +289,16 @@ public void scheduledFlush() {
}
/**
- * Scheduled cleanup of audit data in database.
+ * Scheduled cleanup of audit data in the database.
*/
- @Scheduled(fixedDelayString = "${audit.cleanup.delay.fixed:3600000}", initialDelayString = "${audit.cleanup.delay.initial:1000}")
+ @Scheduled(cron = "${audit.cleanup.cron:0 0 * * * *}")
+ @SchedulerLock(name = "audit.cleanup", lockAtLeastFor = "${audit.cleanup.lockAtLeastFor:5s}", lockAtMostFor = "${audit.cleanup.lockAtMostFor:30m}")
public void scheduledCleanup() {
- logger.debug("Scheduled audit log cleanup called");
+ logger.info("action: scheduledCleanup, state: initiated");
+ LockAssert.assertLocked();
+ logger.info("action: scheduledCleanup, state: lockAsserted");
cleanup();
+ logger.info("action: scheduledCleanup, state: succeeded");
}
/**
diff --git a/audit-base/src/test/java/com/wultra/core/audit/base/AuditTest.java b/audit-base/src/test/java/com/wultra/core/audit/base/AuditTest.java
index e568832..de881ab 100644
--- a/audit-base/src/test/java/com/wultra/core/audit/base/AuditTest.java
+++ b/audit-base/src/test/java/com/wultra/core/audit/base/AuditTest.java
@@ -32,7 +32,7 @@
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(classes = TestApplication.class, properties = {"audit.db.table.param.enabled=false"})
-@Sql(scripts = "/db_schema.sql")
+@Sql("/db_schema.sql")
class AuditTest {
private final AuditFactory auditFactory;
@@ -45,7 +45,7 @@ public AuditTest(AuditFactory auditFactory, JdbcTemplate jdbcTemplate) {
}
@BeforeEach
- public void cleanTestDb() {
+ void cleanTestDb() {
jdbcTemplate.execute("DELETE FROM audit_log");
jdbcTemplate.execute("DELETE FROM audit_param");
}
diff --git a/audit-base/src/test/java/com/wultra/core/audit/base/TestApplication.java b/audit-base/src/test/java/com/wultra/core/audit/base/TestApplication.java
index de9f0df..d97006f 100644
--- a/audit-base/src/test/java/com/wultra/core/audit/base/TestApplication.java
+++ b/audit-base/src/test/java/com/wultra/core/audit/base/TestApplication.java
@@ -15,12 +15,20 @@
*/
package com.wultra.core.audit.base;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
+import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
+import javax.sql.DataSource;
+
@SpringBootApplication
@EnableScheduling
+@EnableSchedulerLock(defaultLockAtMostFor = "30m")
public class TestApplication {
/**
@@ -32,4 +40,13 @@ public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
+ @Bean
+ public LockProvider lockProvider(final DataSource dataSource) {
+ return new JdbcTemplateLockProvider(
+ JdbcTemplateLockProvider.Configuration.builder()
+ .withJdbcTemplate(new JdbcTemplate(dataSource))
+ .usingDbTime()
+ .build()
+ );
+ }
}
diff --git a/audit-base/src/test/java/com/wultra/core/audit/base/database/DatabaseAuditWriterTest.java b/audit-base/src/test/java/com/wultra/core/audit/base/database/DatabaseAuditWriterTest.java
new file mode 100644
index 0000000..36002af
--- /dev/null
+++ b/audit-base/src/test/java/com/wultra/core/audit/base/database/DatabaseAuditWriterTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2025 Wultra s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.wultra.core.audit.base.database;
+
+import com.wultra.core.audit.base.Audit;
+import com.wultra.core.audit.base.AuditFactory;
+import com.wultra.core.audit.base.TestApplication;
+import com.wultra.core.audit.base.model.AuditDetail;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.test.context.jdbc.Sql;
+
+import java.time.Duration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test for {@link DatabaseAuditWriter}.
+ *
+ * @author Lubos Racansky, lubos.racansky@wultra.com
+ */
+class DatabaseAuditWriterTest {
+
+ @SpringBootTest(
+ classes = TestApplication.class,
+ properties = {
+ "audit.cleanup.cron=0/3 * * * * *",
+ "audit.db.cleanup.days=-1" // time shift to the future to enable cleanup test
+ }
+ )
+ @Sql("/db_schema.sql")
+ @Nested
+ class ScheduledCleanupOn {
+
+ @Autowired
+ private AuditFactory auditFactory;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Test
+ void testAuditScheduledCleanup() {
+ final Audit audit = auditFactory.getAudit();
+ audit.info("test message", AuditDetail.builder().param("my_id", "test_id").build());
+ audit.flush();
+
+ assertEquals(1, countAuditLogs(jdbcTemplate));
+
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(5))
+ .until(() -> countAuditLogs(jdbcTemplate) == 0);
+ }
+ }
+
+ @SpringBootTest(
+ classes = TestApplication.class,
+ properties = {
+ "audit.cleanup.cron=-",
+ "audit.db.cleanup.days=-1" // time shift to the future to enable cleanup test
+ }
+ )
+ @Sql("/db_schema.sql")
+ @Nested
+ class ScheduledCleanupOff {
+
+ @Autowired
+ private AuditFactory auditFactory;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Test
+ void testAuditScheduledCleanup() {
+ final Audit audit = auditFactory.getAudit();
+ audit.info("test message", AuditDetail.builder().param("my_id", "test_id").build());
+ audit.flush();
+
+ assertEquals(1, countAuditLogs(jdbcTemplate));
+
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(6))
+ .pollInterval(Duration.ofSeconds(5))
+ .until(() -> countAuditLogs(jdbcTemplate) == 1);
+ }
+ }
+
+ private int countAuditLogs(final JdbcTemplate jdbcTemplate) {
+ final SqlRowSet rs = jdbcTemplate.queryForRowSet("SELECT COUNT(*) FROM audit_log");
+ assertTrue(rs.next());
+ return rs.getInt(1);
+ }
+}
diff --git a/audit-base/src/test/resources/db_schema.sql b/audit-base/src/test/resources/db_schema.sql
index d8a2f26..1e1e058 100644
--- a/audit-base/src/test/resources/db_schema.sql
+++ b/audit-base/src/test/resources/db_schema.sql
@@ -44,3 +44,6 @@ CREATE INDEX audit_param_log ON audit_param (audit_log_id);
CREATE INDEX audit_param_timestamp ON audit_param (timestamp_created);
CREATE INDEX audit_param_key ON audit_param (param_key);
CREATE INDEX audit_param_value ON audit_param (param_value);
+
+-- Shedlock
+CREATE TABLE IF NOT EXISTS shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
\ No newline at end of file
diff --git a/bom/pom.xml b/bom/pom.xml
index 00301f5..c9cca6f 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -7,7 +7,7 @@
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
core-bom
diff --git a/docs/sql/mysql/create_schema.sql b/docs/sql/mysql/create_schema.sql
index 33851eb..c35dfdc 100644
--- a/docs/sql/mysql/create_schema.sql
+++ b/docs/sql/mysql/create_schema.sql
@@ -37,4 +37,7 @@ CREATE INDEX audit_log_type ON audit_log (audit_type);
CREATE INDEX audit_param_log ON audit_param (audit_log_id);
CREATE INDEX audit_param_timestamp ON audit_param (timestamp_created);
CREATE INDEX audit_param_key ON audit_param (param_key);
-CREATE FULLTEXT INDEX audit_param_value ON audit_param (param_value);
\ No newline at end of file
+CREATE FULLTEXT INDEX audit_param_value ON audit_param (param_value);
+
+-- Shedlock
+CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL, locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
\ No newline at end of file
diff --git a/docs/sql/oracle/create_schema.sql b/docs/sql/oracle/create_schema.sql
index a2a257b..d5df5be 100644
--- a/docs/sql/oracle/create_schema.sql
+++ b/docs/sql/oracle/create_schema.sql
@@ -37,4 +37,7 @@ CREATE INDEX audit_log_type ON audit_log (audit_type);
CREATE INDEX audit_param_log ON audit_param (audit_log_id);
CREATE INDEX audit_param_timestamp ON audit_param (timestamp_created);
CREATE INDEX audit_param_key ON audit_param (param_key);
-CREATE INDEX audit_param_value ON audit_param (param_value);
\ No newline at end of file
+CREATE INDEX audit_param_value ON audit_param (param_value);
+
+-- Shedlock
+CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL, locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
\ No newline at end of file
diff --git a/docs/sql/postgresql/create_schema.sql b/docs/sql/postgresql/create_schema.sql
index d969b8e..974892b 100644
--- a/docs/sql/postgresql/create_schema.sql
+++ b/docs/sql/postgresql/create_schema.sql
@@ -37,4 +37,7 @@ CREATE INDEX audit_log_type ON audit_log (audit_type);
CREATE INDEX audit_param_log ON audit_param (audit_log_id);
CREATE INDEX audit_param_timestamp ON audit_param (timestamp_created);
CREATE INDEX audit_param_key ON audit_param (param_key);
-CREATE INDEX audit_param_value ON audit_param (param_value);
\ No newline at end of file
+CREATE INDEX audit_param_value ON audit_param (param_value);
+
+-- Shedlock
+CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
\ No newline at end of file
diff --git a/http-common/pom.xml b/http-common/pom.xml
index e7a65ec..6827669 100644
--- a/http-common/pom.xml
+++ b/http-common/pom.xml
@@ -7,7 +7,7 @@
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
http-common
diff --git a/pom.xml b/pom.xml
index f652dd7..0955530 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
Wultra - Core Java Libraries
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
pom
2017
@@ -56,12 +56,13 @@
17
${java.version}
- 3.13.0
- 3.5.0
+ 3.14.0
+ 3.5.3
3.5.0
- 3.3.4
+ 3.4.6
+ 6.7.0
@@ -119,7 +120,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.10.0
+ 3.11.2
false
@@ -135,7 +136,7 @@
org.apache.maven.plugins
maven-deploy-plugin
- 3.1.3
+ 3.1.4
org.apache.maven.plugins
diff --git a/rest-client-base/pom.xml b/rest-client-base/pom.xml
index d148272..da1323b 100644
--- a/rest-client-base/pom.xml
+++ b/rest-client-base/pom.xml
@@ -6,7 +6,7 @@
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
rest-client-base
diff --git a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java
index a64e19c..4e8f5a6 100644
--- a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java
+++ b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java
@@ -1296,6 +1296,16 @@ public CertificateAuthBuilder keyStoreLocation(String keyStoreLocation) {
return this;
}
+ /**
+ * Set keystore bytes.
+ * @param keyStoreBytes Keystore bytes.
+ * @return Builder.
+ */
+ public CertificateAuthBuilder keyStoreBytes(byte[] keyStoreBytes) {
+ mainBuilder.config.setKeyStoreBytes(keyStoreBytes);
+ return this;
+ }
+
/**
* Set keystore password.
* @param keyStorePassword Keystore password.
@@ -1345,6 +1355,16 @@ public CertificateAuthBuilder trustStoreLocation(String trustStoreLocation) {
return this;
}
+ /**
+ * Set truststore bytes.
+ * @param trustStoreBytes Truststore bytes.
+ * @return Builder.
+ */
+ public CertificateAuthBuilder trustStoreBytes(byte[] trustStoreBytes) {
+ mainBuilder.config.setTrustStoreBytes(trustStoreBytes);
+ return this;
+ }
+
/**
* Set truststore password.
* @param trustStorePassword Truststore password.
diff --git a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/RestClientConfiguration.java b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/RestClientConfiguration.java
index f00ee1b..0150b0a 100644
--- a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/RestClientConfiguration.java
+++ b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/RestClientConfiguration.java
@@ -543,6 +543,10 @@ public byte[] getKeyStoreBytes() {
* @param keyStoreBytes Byte data with the key store.
*/
public void setKeyStoreBytes(byte[] keyStoreBytes) {
+ if (keyStoreBytes == null) {
+ this.keyStoreBytes = null;
+ return;
+ }
this.keyStoreBytes = Arrays.copyOf(keyStoreBytes, keyStoreBytes.length);
}
@@ -643,6 +647,10 @@ public byte[] getTrustStoreBytes() {
* @param trustStoreBytes Byte data with the trust store.
*/
public void setTrustStoreBytes(byte[] trustStoreBytes) {
+ if (trustStoreBytes == null) {
+ this.trustStoreBytes = null;
+ return;
+ }
this.trustStoreBytes = Arrays.copyOf(trustStoreBytes, trustStoreBytes.length);
}
diff --git a/rest-model-base/pom.xml b/rest-model-base/pom.xml
index 5c28381..45bc586 100644
--- a/rest-model-base/pom.xml
+++ b/rest-model-base/pom.xml
@@ -6,7 +6,7 @@
io.getlime.core
lime-java-core-parent
- 1.11.0
+ 1.12.0-SNAPSHOT
rest-model-base