diff --git a/aws-modules/aws-s3/README.md b/aws-modules/aws-s3/README.md
index cb039b7c61..9b862c8685 100644
--- a/aws-modules/aws-s3/README.md
+++ b/aws-modules/aws-s3/README.md
@@ -4,7 +4,7 @@ This module contains articles about Simple Storage Service (S3) on AWS
### Relevant articles
-- [AWS S3 with Java](https://www.baeldung.com/aws-s3-java)
+- [AWS S3 with Java](https://www.baeldung.com/java-aws-s3)
- [Multipart Uploads in Amazon S3 with Java](https://www.baeldung.com/aws-s3-multipart-upload)
- [Using the JetS3t Java Client With Amazon S3](https://www.baeldung.com/jets3t-amazon-s3)
- [Check if a Specified Key Exists in a Given S3 Bucket Using Java](https://www.baeldung.com/java-aws-s3-check-specified-key-exists)
diff --git a/core-java-modules/core-java-21/README.md b/core-java-modules/core-java-21/README.md
index c63f3b360b..2e7130c906 100644
--- a/core-java-modules/core-java-21/README.md
+++ b/core-java-modules/core-java-21/README.md
@@ -1 +1,2 @@
-## Relevant Articles
\ No newline at end of file
+## Relevant Articles
+- [Sequenced Collections in Java 21](https://www.baeldung.com/java-21-sequenced-collections)
diff --git a/core-java-modules/core-java-8-datetime-2/README.md b/core-java-modules/core-java-8-datetime-2/README.md
index b860ca979d..ac1e1ca81f 100644
--- a/core-java-modules/core-java-8-datetime-2/README.md
+++ b/core-java-modules/core-java-8-datetime-2/README.md
@@ -5,4 +5,5 @@
- [Parsing Date Strings with Varying Formats](https://www.baeldung.com/java-parsing-dates-many-formats)
- [How Many Days Are There in a Particular Month of a Given Year?](https://www.baeldung.com/days-particular-month-given-year)
- [Difference Between Instant and LocalDateTime](https://www.baeldung.com/java-instant-vs-localdatetime)
+- [Add Minutes to a Time String in Java](https://www.baeldung.com/java-string-time-add-mins)
- [[<-- Prev]](/core-java-modules/core-java-datetime-java8-1)
diff --git a/core-java-modules/core-java-8-datetime/src/test/java/com/baeldung/dateapi/JavaDurationUnitTest.java b/core-java-modules/core-java-8-datetime/src/test/java/com/baeldung/dateapi/JavaDurationUnitTest.java
index be43fb609a..d1bd77776a 100644
--- a/core-java-modules/core-java-8-datetime/src/test/java/com/baeldung/dateapi/JavaDurationUnitTest.java
+++ b/core-java-modules/core-java-8-datetime/src/test/java/com/baeldung/dateapi/JavaDurationUnitTest.java
@@ -3,9 +3,11 @@ package com.baeldung.dateapi;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
@@ -19,7 +21,7 @@ public class JavaDurationUnitTest {
LocalTime finalTime = initialTime.plus(Duration.ofSeconds(30));
long seconds = Duration.between(initialTime, finalTime)
- .getSeconds();
+ .getSeconds();
assertThat(seconds).isEqualTo(30);
}
@@ -34,6 +36,22 @@ public class JavaDurationUnitTest {
assertThat(seconds).isEqualTo(30);
}
+ @Test
+ public void givenADuration_whenCallingisZeroAndisNegative_thenGetExpectedResult() {
+ LocalDateTime start = LocalDateTime.parse("2020-01-01T08:00:00");
+ LocalDateTime end = LocalDateTime.parse("2020-01-01T12:00:00");
+ assertFalse(Duration.between(start, end)
+ .isNegative());
+ assertTrue(Duration.between(end, start)
+ .isNegative());
+
+ LocalDateTime theTime = LocalDateTime.parse("2023-09-09T08:00:00");
+ assertTrue(Duration.between(theTime, theTime)
+ .isZero());
+ assertFalse(Duration.between(theTime, theTime)
+ .isNegative());
+ }
+
@Test
public void test2() {
Instant start = Instant.parse("2017-10-03T10:15:30.00Z");
@@ -53,17 +71,17 @@ public class JavaDurationUnitTest {
assertEquals(1, fromMinutes.toHours());
assertEquals(120, duration.plusSeconds(60)
- .getSeconds());
+ .getSeconds());
assertEquals(30, duration.minusSeconds(30)
- .getSeconds());
+ .getSeconds());
assertEquals(120, duration.plus(60, ChronoUnit.SECONDS)
- .getSeconds());
+ .getSeconds());
assertEquals(30, duration.minus(30, ChronoUnit.SECONDS)
- .getSeconds());
+ .getSeconds());
Duration fromChar1 = Duration.parse("P1DT1H10M10.5S");
Duration fromChar2 = Duration.parse("PT10M");
}
-}
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-collections-set-2/README.md b/core-java-modules/core-java-collections-set-2/README.md
index feeffed1c4..ee41908faf 100644
--- a/core-java-modules/core-java-collections-set-2/README.md
+++ b/core-java-modules/core-java-collections-set-2/README.md
@@ -5,4 +5,5 @@
- [Sorting a HashSet in Java](https://www.baeldung.com/java-sort-hashset)
- [How to Get First Item From a Java Set](https://www.baeldung.com/first-item-set)
- [Cartesian Product of Any Number of Sets in Java](https://www.baeldung.com/java-cartesian-product-sets)
+- [How to Get Index of an Item in Java Set](https://www.baeldung.com/java-set-element-find-index)
- More articles: [[<-- prev]](/core-java-modules/core-java-collections-set)
diff --git a/core-java-modules/core-java-concurrency-collections-2/README.md b/core-java-modules/core-java-concurrency-collections-2/README.md
index 2d65cf88e7..fdb7af5936 100644
--- a/core-java-modules/core-java-concurrency-collections-2/README.md
+++ b/core-java-modules/core-java-concurrency-collections-2/README.md
@@ -5,4 +5,5 @@
- [Java Concurrent HashSet Equivalent to ConcurrentHashMap](https://www.baeldung.com/java-concurrent-hashset-concurrenthashmap)
- [Reading and Writing With a ConcurrentHashMap](https://www.baeldung.com/concurrenthashmap-reading-and-writing)
- [ArrayBlockingQueue vs. LinkedBlockingQueue](https://www.baeldung.com/java-arrayblockingqueue-vs-linkedblockingqueue)
+- [Difference Between Hashtable and ConcurrentHashMap in Java](https://www.baeldung.com/java-hashtable-vs-concurrenthashmap)
- [[<-- Prev]](/core-java-modules/core-java-concurrency-collections)
diff --git a/core-java-modules/core-java-lang-oop-inheritance/README.md b/core-java-modules/core-java-lang-oop-inheritance/README.md
index 430f88e717..6091b15024 100644
--- a/core-java-modules/core-java-lang-oop-inheritance/README.md
+++ b/core-java-modules/core-java-lang-oop-inheritance/README.md
@@ -12,4 +12,4 @@ This module contains articles about inheritance in Java
- [Guide to Inheritance in Java](https://www.baeldung.com/java-inheritance)
- [Object Type Casting in Java](https://www.baeldung.com/java-type-casting)
- [Variable and Method Hiding in Java](https://www.baeldung.com/java-variable-method-hiding)
-- [Inner Classes Vs. Subclasses in Java](https://www.baeldung.com/java-inner-classes-vs-subclasses)
+- [Inner Classes vs. Subclasses in Java](https://www.baeldung.com/java-inner-classes-vs-subclasses)
diff --git a/core-java-modules/core-java-streams-5/README.md b/core-java-modules/core-java-streams-5/README.md
index 885949d937..dec4a7a0cb 100644
--- a/core-java-modules/core-java-streams-5/README.md
+++ b/core-java-modules/core-java-streams-5/README.md
@@ -2,3 +2,4 @@
- [Difference Between parallelStream() and stream().parallel() in Java](https://www.baeldung.com/java-parallelstream-vs-stream-parallel)
- [Working With Empty Stream in Java](https://www.baeldung.com/java-empty-stream)
- [Aggregate Runtime Exceptions in Java Streams](https://www.baeldung.com/java-streams-aggregate-exceptions)
+- [Streams vs. Loops in Java](https://www.baeldung.com/java-streams-vs-loops)
diff --git a/core-java-modules/core-java-string-operations-5/README.md b/core-java-modules/core-java-string-operations-5/README.md
index 21ba1bf985..fda7f39654 100644
--- a/core-java-modules/core-java-string-operations-5/README.md
+++ b/core-java-modules/core-java-string-operations-5/README.md
@@ -10,4 +10,5 @@
- [Guide to Splitting a String by Whitespace in Java](https://www.baeldung.com/java-splitting-a-string-by-whitespace)
- [Check if the First Letter of a String Is a Number](https://www.baeldung.com/java-check-if-string-starts-with-number)
- [Print “” Quotes Around a String in Java](https://www.baeldung.com/java-string-print-quotes)
-- [Remove Punctuation From a String in Java](https://www.baeldung.com/java-remove-punctuation-from-string)
\ No newline at end of file
+- [Remove Punctuation From a String in Java](https://www.baeldung.com/java-remove-punctuation-from-string)
+- [Replacing Single Quote with \’ in Java String](https://www.baeldung.com/java-replacing-single-quote-string)
diff --git a/gradle-modules/gradle-5/README.md b/gradle-modules/gradle-5/README.md
index e37c100534..7871c0e822 100644
--- a/gradle-modules/gradle-5/README.md
+++ b/gradle-modules/gradle-5/README.md
@@ -2,3 +2,4 @@
- [Run a Java main Method Using Gradle](https://www.baeldung.com/gradle-run-java-main)
- [Finding Unused Gradle Dependencies](https://www.baeldung.com/gradle-finding-unused-dependencies)
+- [Intro to Gradle Lint Plugin](https://www.baeldung.com/java-gradle-lint-intro)
diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index ec02b0f37c..73c6b96bba 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -114,7 +114,7 @@
java-mongodb
questdb
neo4j
- rethinkdb
+
scylladb
spring-data-cassandra-2
spring-data-jpa-repo-3
diff --git a/persistence-modules/spring-data-cassandra-2/README.md b/persistence-modules/spring-data-cassandra-2/README.md
index 3f49d6ae93..0578dcc429 100644
--- a/persistence-modules/spring-data-cassandra-2/README.md
+++ b/persistence-modules/spring-data-cassandra-2/README.md
@@ -2,3 +2,4 @@
- [Using Test Containers With Spring Data Cassandra](https://www.baeldung.com/spring-data-cassandra-test-containers)
- [Cassandra – Object Mapping with DataStax Java Driver](https://www.baeldung.com/cassandra-object-mapping-datastax-java-driver)
+- [Query With IN Clause in Spring Data Cassandra](https://www.baeldung.com/spring-cassandra-query-in-clause)
diff --git a/persistence-modules/spring-data-couchbase-2/src/test/docker/Dockerfile b/persistence-modules/spring-data-couchbase-2/src/test/docker/Dockerfile
new file mode 100644
index 0000000000..1dffcb31f1
--- /dev/null
+++ b/persistence-modules/spring-data-couchbase-2/src/test/docker/Dockerfile
@@ -0,0 +1,4 @@
+FROM couchbase/server:community-5.0.1
+
+COPY configure.sh /configure.sh
+CMD ["/configure.sh"]
\ No newline at end of file
diff --git a/persistence-modules/spring-data-couchbase-2/src/test/docker/configure.sh b/persistence-modules/spring-data-couchbase-2/src/test/docker/configure.sh
new file mode 100644
index 0000000000..c2b62dba7c
--- /dev/null
+++ b/persistence-modules/spring-data-couchbase-2/src/test/docker/configure.sh
@@ -0,0 +1,106 @@
+#!/bin/bash
+
+function retry() {
+ for i in $(seq 1 10); do
+ $1 "$2"
+ if [[ $? == 0 ]]; then
+ return 0
+ fi
+ sleep 1
+ done
+ return 1
+}
+
+function bucketCreate(){
+ couchbase-cli bucket-create -c localhost -u Administrator -p password \
+ --bucket="$1" \
+ --bucket-type=couchbase \
+ --bucket-ramsize=512 \
+ --bucket-replica=1 \
+ --wait
+ if [[ $? != 0 ]]; then
+ return 1
+ fi
+}
+
+function userCreate(){
+ createOutput=$(couchbase-cli user-manage -c localhost -u Administrator -p password \
+ --set --rbac-username "$1" --rbac-password "$1" \
+ --roles admin --auth-domain local)
+ if [[ $? != 0 ]]; then
+ echo $createOutput >&2
+ return 1
+ fi
+}
+
+function clusterUp(){
+ # wait for service to come up
+ until $(curl --output /dev/null --silent --head --fail http://localhost:8091); do
+ printf '.'
+ sleep 1
+ done
+
+ # initialize cluster
+ initOutput=$(couchbase-cli cluster-init -c localhost \
+ --cluster-username=Administrator \
+ --cluster-password=password \
+ --cluster-port=8091 \
+ --services=data,index,query,fts \
+ --cluster-ramsize=1024 \
+ --cluster-index-ramsize=256 \
+ --cluster-fts-ramsize=256 \
+ --index-storage-setting=default)
+ if [[ $? != 0 ]]; then
+ echo $initOutput >&2
+ return 1
+ fi
+}
+
+function main(){
+ set -ex
+ echo "Couchbase UI :8091"
+ echo "Couchbase logs /opt/couchbase/var/lib/couchbase/logs"
+ ./entrypoint.sh couchbase-server &
+ if [[ $? != 0 ]]; then
+ echo "Couchbase startup failed. Exiting." >&2
+ exit 1
+ fi
+
+ clusterUp
+ if [[ $? != 0 ]]; then
+ echo "Cluster init failed. Exiting." >&2
+ exit 1
+ fi
+
+ retry userCreate baeldung
+ if [[ $? != 0 ]]; then
+ echo "User create failed. Exiting." >&2
+ exit 1
+ fi
+
+ retry userCreate baeldung2
+ if [[ $? != 0 ]]; then
+ echo "User create failed. Exiting." >&2
+ exit 1
+ fi
+
+ retry bucketCreate baeldung
+ if [[ $? != 0 ]]; then
+ echo "Bucket create failed. Exiting." >&2
+ exit 1
+ fi
+
+ retry bucketCreate baeldung2
+ if [[ $? != 0 ]]; then
+ echo "Bucket create failed. Exiting." >&2
+ exit 1
+ fi
+
+ set +ex
+
+ # entrypoint.sh launches the server but since config.sh is pid 1 we keep it
+ # running so that the docker container does not exit.
+ wait
+}
+
+main
\ No newline at end of file
diff --git a/persistence-modules/spring-data-couchbase-2/src/test/docker/dockerbuild.sh b/persistence-modules/spring-data-couchbase-2/src/test/docker/dockerbuild.sh
new file mode 100644
index 0000000000..37add165c6
--- /dev/null
+++ b/persistence-modules/spring-data-couchbase-2/src/test/docker/dockerbuild.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# setup
+set -ex
+docker rm couchbase_container -f
+
+# main
+docker build -t couchbase_image .
+
+# cleanup
+set +ex
+
+# run
+docker run -d --name couchbase_container -p 8091-8096:8091-8096 -p 11210-11211:11210-11211 couchbase_image
diff --git a/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/SpringContextLiveTest.java b/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/SpringContextLiveTest.java
index 553520e6e6..74dccbadd0 100644
--- a/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/SpringContextLiveTest.java
+++ b/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/SpringContextLiveTest.java
@@ -12,16 +12,10 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
/**
* This LiveTest requires:
*
- * 1- Couchbase instance running (e.g. with `docker run -d --name db -p 8091-8096:8091-8096 -p 11210-11211:11210-11211 couchbase`)
- *
- *
- * 2- Couchbase configured with (we can use the console in localhost:8091):
- *
- * 2.1- Buckets: named 'baeldung' and 'baeldung2'
- *
- * 2.2- Security: users 'baeldung' and 'baeldung2'. Note: in newer versions an empty password is not allowed, then we have to change the passwords in the project)
- *
- * 2.3- Spacial View: Add new spacial view (in Index tab) in document 'campus_spatial', view 'byLocation' with the following function:
+ * 1- Couchbase 5 instance Running and Configured.
+ * It's enough to execute the "dockerbuild.sh" script in the test/docker folder.
+ *
+ * 2.1- Spacial View: Add new spacial view (in Index tab) in document 'campus_spatial', view 'byLocation' with the following function:
* {@code
* function (doc) {
* if (doc.location &&
@@ -30,8 +24,9 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
* }
* }}
*
- * 2.4- MapReduce Views: Add new views in document 'campus':
- * 2.4.1- view 'all' with function:
+ * 2.2- MapReduce Views: Add new views in document 'campus':
+ *
+ * 2.2.1- view 'all' with function:
* {@code
* function (doc, meta) {
* if(doc._class == "com.baeldung.spring.data.couchbase.model.Campus") {
@@ -39,7 +34,7 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
* }
* }}
*
- * 2.4.2- view 'byName' with function:
+ * 2.2.2- view 'byName' with function:
* {@code
* function (doc, meta) {
* if(doc._class == "com.baeldung.spring.data.couchbase.model.Campus" &&
diff --git a/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/spring/data/couchbase/CustomTypeKeyCouchbaseConfig.java b/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/spring/data/couchbase/CustomTypeKeyCouchbaseConfig.java
deleted file mode 100644
index eacecb4e1e..0000000000
--- a/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/spring/data/couchbase/CustomTypeKeyCouchbaseConfig.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.baeldung.spring.data.couchbase;
-
-import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
-
-public class CustomTypeKeyCouchbaseConfig extends MyCouchbaseConfig {
-
- @Override
- public String getConnectionString() {
- return NODE_LIST;
- }
-
- @Override
- public String getUserName() {
- return BUCKET_USERNAME;
- }
-
- @Override
- public String getPassword() {
- return BUCKET_PASSWORD;
- }
-
- @Override
- public String typeKey() {
- return MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE;
- }
-}
diff --git a/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/spring/data/couchbase/ReadYourOwnWritesCouchbaseConfig.java b/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/spring/data/couchbase/ReadYourOwnWritesCouchbaseConfig.java
deleted file mode 100644
index 33a6c4f091..0000000000
--- a/persistence-modules/spring-data-couchbase-2/src/test/java/com/baeldung/spring/data/couchbase/ReadYourOwnWritesCouchbaseConfig.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.baeldung.spring.data.couchbase;
-
-import com.couchbase.client.java.query.QueryScanConsistency;
-
-public class ReadYourOwnWritesCouchbaseConfig extends MyCouchbaseConfig {
-
- @Override
- public QueryScanConsistency getDefaultConsistency() {
- return QueryScanConsistency.REQUEST_PLUS;
- }
-}
diff --git a/spring-aop-2/README.md b/spring-aop-2/README.md
index a9694ac236..0aa20d32f0 100644
--- a/spring-aop-2/README.md
+++ b/spring-aop-2/README.md
@@ -7,4 +7,5 @@ This module contains articles about Spring aspect oriented programming (AOP)
- [Spring Performance Logging](https://www.baeldung.com/spring-performance-logging)
- [When Does Java Throw UndeclaredThrowableException?](https://www.baeldung.com/java-undeclaredthrowableexception)
- [Get Advised Method Info in Spring AOP](https://www.baeldung.com/spring-aop-get-advised-method-info)
-- More articles: [[<-- prev]](/spring-aop)
\ No newline at end of file
+- [Invoke Spring @Cacheable from Another Method of Same Bean](https://www.baeldung.com/spring-invoke-cacheable-other-method-same-bean)
+- More articles: [[<-- prev]](/spring-aop)
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/SecuredEcommerceApplication.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/SecuredEcommerceApplication.java
new file mode 100644
index 0000000000..150fe399d1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/SecuredEcommerceApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.permitallanonymous;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan("com.baeldung.permitallanonymous.*")
+public class SecuredEcommerceApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(SecuredEcommerceApplication.class, args);
+ }
+}
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/controller/EcommerceController.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/controller/EcommerceController.java
new file mode 100644
index 0000000000..8f20baee10
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/controller/EcommerceController.java
@@ -0,0 +1,29 @@
+package com.baeldung.permitallanonymous.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class EcommerceController {
+
+ //can be accessed by only logged-in users
+ @GetMapping("/private/showCart")
+ public @ResponseBody String showCart() {
+ return "Show Cart";
+ }
+
+ //can we accessed by both anonymous and authenticated users
+ @GetMapping("/public/showProducts")
+ public @ResponseBody String listProducts() {
+ return "List Products";
+ }
+
+ //can be access by only anonymous users not by authenticated users
+ @GetMapping("/public/registerUser")
+ public @ResponseBody String registerUser() {
+ return "Register User";
+ }
+
+}
+
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/filter/AuditInterceptor.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/filter/AuditInterceptor.java
new file mode 100644
index 0000000000..c0a5f6972f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/filter/AuditInterceptor.java
@@ -0,0 +1,33 @@
+package com.baeldung.permitallanonymous.filter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class AuditInterceptor extends OncePerRequestFilter {
+ private final Logger logger = LoggerFactory.getLogger(AuditInterceptor.class);
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication instanceof AnonymousAuthenticationToken) {
+ logger.info("Audit anonymous user");
+ }
+ if (authentication instanceof UsernamePasswordAuthenticationToken) {
+ logger.info("Audit registered user");
+ }
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/security/EcommerceWebSecurityConfig.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/security/EcommerceWebSecurityConfig.java
new file mode 100644
index 0000000000..566ec49e42
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/permitallanonymous/security/EcommerceWebSecurityConfig.java
@@ -0,0 +1,46 @@
+package com.baeldung.permitallanonymous.security;
+
+import com.baeldung.permitallanonymous.filter.AuditInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+public class EcommerceWebSecurityConfig {
+ @Bean
+ public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
+ UserDetails user = User.withUsername("spring")
+ .password(passwordEncoder.encode("secret"))
+ .roles("USER")
+ .build();
+
+ return new InMemoryUserDetailsManager(user);
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.addFilterAfter(new AuditInterceptor(), AnonymousAuthenticationFilter.class)
+ .authorizeRequests()
+ .antMatchers("/private/**").authenticated().and().httpBasic()
+ .and().authorizeRequests()
+ .antMatchers("/public/showProducts").permitAll()
+ .antMatchers("/public/registerUser").anonymous();
+
+ return http.build();
+ }
+
+ @Bean
+ public BCryptPasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/permitallanonymous/SecureEcommerceApplicationUnitTest.java b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/permitallanonymous/SecureEcommerceApplicationUnitTest.java
new file mode 100644
index 0000000000..3c73caf1fd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/permitallanonymous/SecureEcommerceApplicationUnitTest.java
@@ -0,0 +1,70 @@
+package com.baeldung.permitallanonymous;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SecuredEcommerceApplication.class)
+@AutoConfigureMockMvc
+public class SecureEcommerceApplicationUnitTest {
+ @Autowired
+ private MockMvc mockMvc;
+ private static final Logger logger = LoggerFactory.getLogger(SecureEcommerceApplicationUnitTest.class);
+
+ @WithAnonymousUser
+ @Test
+ public void givenAnonymousUser_whenAccessToUserRegisterPage_thenAllowAccess() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/public/registerUser"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string("Register User"));
+ }
+
+ @WithMockUser(username = "spring", password = "secret")
+ @Test
+ public void givenAuthenticatedUser_whenAccessToUserRegisterPage_thenDenyAccess() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/public/registerUser"))
+ .andExpect(MockMvcResultMatchers.status().isForbidden());
+ }
+
+ @WithMockUser(username = "spring", password = "secret")
+ @Test
+ public void givenAuthenticatedUser_whenAccessToProductLinePage_thenAllowAccess() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/public/showProducts"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string("List Products"));
+ }
+
+ @WithAnonymousUser
+ @Test
+ public void givenAnonymousUser_whenAccessToProductLinePage_thenAllowAccess() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/public/showProducts"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string("List Products"));
+ }
+
+ @WithMockUser(username = "spring", password = "secret")
+ @Test
+ public void givenAuthenticatedUser_whenAccessToCartPage_thenAllowAccess() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/private/showCart"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string("Show Cart"));
+ }
+
+ @WithAnonymousUser
+ @Test
+ public void givenAnonymousUser_whenAccessToCartPage_thenDenyAccess() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/private/showCart"))
+ .andExpect(MockMvcResultMatchers.status().isUnauthorized());
+ }
+}
diff --git a/spring-boot-modules/spring-boot-ssl-bundles/README.md b/spring-boot-modules/spring-boot-ssl-bundles/README.md
index 5616cce48b..840cc21583 100644
--- a/spring-boot-modules/spring-boot-ssl-bundles/README.md
+++ b/spring-boot-modules/spring-boot-ssl-bundles/README.md
@@ -1 +1,2 @@
## Relevant Articles
+- [Securing Spring Boot 3 Applications With SSL Bundles](https://www.baeldung.com/spring-boot-security-ssl-bundles)
diff --git a/spring-vault/README.md b/spring-vault/README.md
index 9e1d14ba6b..22fb0a7ff2 100644
--- a/spring-vault/README.md
+++ b/spring-vault/README.md
@@ -5,3 +5,4 @@ This module contains articles about Spring Vault
### Relevant Articles:
- [Spring Vault](https://www.baeldung.com/spring-vault)
+- [Secure Kubernetes Secrets with Vault](https://www.baeldung.com/spring-vault-kubernetes-secrets)
diff --git a/testing-modules/junit-5-basics-2/README.md b/testing-modules/junit-5-basics-2/README.md
index f5e2558332..0e0faedb4b 100644
--- a/testing-modules/junit-5-basics-2/README.md
+++ b/testing-modules/junit-5-basics-2/README.md
@@ -1,2 +1,2 @@
### Relevant Articles:
-- [Test Main Method with JUnit](http://www.baeldung.com/junit-5)
+- [Test Main Method with JUnit](https://www.baeldung.com/junit-test-main-method)
diff --git a/testing-modules/mockito-2/README.md b/testing-modules/mockito-2/README.md
index 82c97305f6..b60885c6d5 100644
--- a/testing-modules/mockito-2/README.md
+++ b/testing-modules/mockito-2/README.md
@@ -7,3 +7,4 @@ This module contains articles about Mockito
- [Resolving Mockito Exception: Wanted But Not Invoked](https://www.baeldung.com/mockito-exception-wanted-but-not-invoked)
- [Matching Null With Mockito](https://www.baeldung.com/mockito-match-null)
- [Mock Same Method with Different Parameters](https://www.baeldung.com/java-mock-same-method-other-parameters)
+- [How to Mock Constructors for Unit Testing using Mockito](https://www.baeldung.com/java-mockito-constructors-unit-testing)
diff --git a/testing-modules/testing-assertions/README.md b/testing-modules/testing-assertions/README.md
index 37de3d4929..8f94277791 100644
--- a/testing-modules/testing-assertions/README.md
+++ b/testing-modules/testing-assertions/README.md
@@ -5,3 +5,4 @@
- [Assert That a Java Optional Has a Certain Value](https://www.baeldung.com/java-optional-assert-value)
- [Assert That an Object Is From a Specific Type](https://www.baeldung.com/java-assert-object-of-type)
- [Asserting Equality on Two Classes Without an equals() Method](https://www.baeldung.com/java-assert-equality-no-equals)
+- [Assert Regex Matches in JUnit](https://www.baeldung.com/junit-assert-regex-matches)